[Infra UI] Disable Infrastructure and Metrics alerts in Serverless (#167978)

Closes https://github.com/elastic/kibana/issues/164683

## Summary

This PR disables the infrastructure, metrics and logs alerts rule in
Serverless:
- Deletes the code responsible for the "Metric Anomaly" rule as it was
[previously disabled](https://github.com/elastic/kibana/pull/93813) with
plans to re-enable it as the previous PR describes but that never
happened.
- Adds feature flags for all three types of alert rules
- Prevents rules registration in serverless based on the feature flags
- Adds logic for showing/hiding items in the "Alerts and rules" dropdown
- Disables custom threshold rule in the Infra UI by default in
serverless as the rule needs to first be enabled by default by
@elastic/actionable-observability team
([context](https://elastic.slack.com/archives/C023GDA0WMP/p1696853751040269))

**Dropdown**
![CleanShot 2023-10-05 at 15 22
48@2x](fb7344c6-b5ee-4020-bd69-473dcd6be446)

**Host details**
![CleanShot 2023-10-05 at 15 23
02@2x](8164f82b-323c-4a2a-8cdc-c65a6c0f0c63)

### How to test

- Checkout locally Run in Serveless mode
- Enable, Infra plugin, custom threshold in Infra, and custom threshold
rule in general:
```
xpack.infra.enabled: true
xpack.infra.featureFlags.customThresholdAlertsEnabled: true
xpack.observability.unsafe.thresholdRule.enabled: true
```
- Go to `/app/metrics/hosts` and make sure there are no "Infrastructure"
and "Metrics" items in the "Alerts and rules" dropdown
- Click on "Manage rules" in the "Alerts and rules" dropdown, then
"Create rule" to open the rule selection flyout
- Make sure there are no rules for "Inventory", "Metrics" or "Logs"
threshold
- Run Kibana in traditional mode
- Make sure the "Alerts and rules" dropdown looks as usual and you can
create "Infrastructure" and "Metrics" alerts

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Mykola Harmash 2023-10-12 15:24:30 +02:00 committed by GitHub
parent b60517ac53
commit 3e8058bdf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 238 additions and 1656 deletions

View file

@ -268,25 +268,17 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.index_management.enableIndexStats (any)',
'xpack.infra.sources.default.fields.message (array)',
/**
* xpack.infra.featureFlags.customThresholdAlertsEnabled is conditional based on traditional/serverless offering
* and will resolve to (boolean)
*/
'xpack.infra.featureFlags.customThresholdAlertsEnabled (any)',
/**
* xpack.infra.featureFlags.logsUIEnabled is conditional based on traditional/serverless offering
* and will resolve to (boolean)
*/
'xpack.infra.featureFlags.logsUIEnabled (any)',
/**
* xpack.infra.featureFlags.metricsExplorerEnabled is conditional based on traditional/serverless offering
* and will resolve to (boolean)
* Feature flags bellow are conditional based on traditional/serverless offering
* and will all resolve to xpack.infra.featureFlags.* (boolean)
*/
'xpack.infra.featureFlags.metricsExplorerEnabled (any)',
/**
* xpack.infra.featureFlags.osqueryEnabled is conditional based on traditional/serverless offering
* and will resolve to (boolean)
*/
'xpack.infra.featureFlags.customThresholdAlertsEnabled (any)',
'xpack.infra.featureFlags.osqueryEnabled (any)',
'xpack.infra.featureFlags.inventoryThresholdAlertRuleEnabled (any)',
'xpack.infra.featureFlags.metricThresholdAlertRuleEnabled (any)',
'xpack.infra.featureFlags.logThresholdAlertRuleEnabled (any)',
'xpack.infra.featureFlags.logsUIEnabled (any)',
'xpack.license_management.ui.enabled (boolean)',
'xpack.maps.preserveDrawingBuffer (boolean)',
'xpack.maps.showMapsInspectorAdapter (boolean)',

View file

@ -37,7 +37,6 @@ describe('rule_type_registry_deprecated_consumers', () => {
"siem.newTermsRule",
"siem.notifications",
"slo.rules.burnRate",
"metrics.alert.anomaly",
"logs.alert.document.count",
"metrics.alert.inventory.threshold",
"metrics.alert.threshold",

View file

@ -30,7 +30,6 @@ export const ruleTypeIdWithValidLegacyConsumers: Record<string, string[]> = {
'siem.newTermsRule': [ALERTS_FEATURE_ID],
'siem.notifications': [ALERTS_FEATURE_ID],
'slo.rules.burnRate': [ALERTS_FEATURE_ID],
'metrics.alert.anomaly': [ALERTS_FEATURE_ID],
'logs.alert.document.count': [ALERTS_FEATURE_ID],
'metrics.alert.inventory.threshold': [ALERTS_FEATURE_ID],
'metrics.alert.threshold': [ALERTS_FEATURE_ID],

View file

@ -12,18 +12,15 @@ import { InventoryItemType, SnapshotMetricType } from '../../inventory_models/ty
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 InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
Anomaly = 'metrics.alert.anomaly',
}
export interface InfraRuleTypeParams {
[InfraRuleType.MetricThreshold]: MetricThresholdParams;
[InfraRuleType.InventoryThreshold]: InventoryMetricConditions;
[InfraRuleType.Anomaly]: MetricAnomalyParams;
}
export enum Comparator {

View file

@ -30,6 +30,9 @@ export interface InfraConfig {
logsUIEnabled: boolean;
metricsExplorerEnabled: boolean;
osqueryEnabled: boolean;
inventoryThresholdAlertRuleEnabled: boolean;
metricThresholdAlertRuleEnabled: boolean;
logThresholdAlertRuleEnabled: boolean;
};
}

View file

@ -7,11 +7,10 @@
import { i18n } from '@kbn/i18n';
import React, { useState, useCallback, useMemo } from 'react';
import {
EuiPopover,
EuiHeaderLink,
EuiContextMenu,
import { EuiPopover, EuiHeaderLink, EuiContextMenu } from '@elastic/eui';
import type {
EuiContextMenuPanelDescriptor,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
@ -23,6 +22,118 @@ import { InfraClientStartDeps } from '../../../types';
type VisibleFlyoutType = 'inventory' | 'metricThreshold' | 'customThreshold';
interface ContextMenuEntries {
items: EuiContextMenuPanelItemDescriptor[];
panels: EuiContextMenuPanelDescriptor[];
}
function useInfrastructureMenu(
onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
): ContextMenuEntries {
const { featureFlags } = usePluginConfig();
return useMemo(() => {
if (!featureFlags.inventoryThresholdAlertRuleEnabled) {
return { items: [], panels: [] };
}
return {
items: [
{
'data-test-subj': 'inventory-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', {
defaultMessage: 'Infrastructure',
}),
panel: 1,
},
],
panels: [
{
id: 1,
title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', {
defaultMessage: 'Infrastructure rules',
}),
items: [
{
'data-test-subj': 'inventory-alerts-create-rule',
name: i18n.translate('xpack.infra.alerting.createInventoryRuleButton', {
defaultMessage: 'Create inventory rule',
}),
onClick: () => onCreateRuleClick('inventory'),
},
],
},
],
};
}, [featureFlags.inventoryThresholdAlertRuleEnabled, onCreateRuleClick]);
}
function useMetricsMenu(
onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
): ContextMenuEntries {
const { featureFlags } = usePluginConfig();
return useMemo(() => {
if (!featureFlags.metricThresholdAlertRuleEnabled) {
return { items: [], panels: [] };
}
return {
items: [
{
'data-test-subj': 'metrics-threshold-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', {
defaultMessage: 'Metrics',
}),
panel: 2,
},
],
panels: [
{
id: 2,
title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', {
defaultMessage: 'Metrics rules',
}),
items: [
{
'data-test-subj': 'metrics-threshold-alerts-create-rule',
name: i18n.translate('xpack.infra.alerting.createThresholdRuleButton', {
defaultMessage: 'Create threshold rule',
}),
onClick: () => onCreateRuleClick('metricThreshold'),
},
],
},
],
};
}, [featureFlags.metricThresholdAlertRuleEnabled, onCreateRuleClick]);
}
function useCustomThresholdMenu(
onCreateRuleClick: (flyoutType: VisibleFlyoutType) => void
): ContextMenuEntries {
const { featureFlags } = usePluginConfig();
return useMemo(() => {
if (!featureFlags.customThresholdAlertsEnabled) {
return { items: [], panels: [] };
}
return {
items: [
{
'data-test-subj': 'custom-threshold-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.customThresholdDropdownMenu', {
defaultMessage: 'Create custom threshold rule',
}),
onClick: () => onCreateRuleClick('customThreshold'),
},
],
panels: [],
};
}, [featureFlags.customThresholdAlertsEnabled, onCreateRuleClick]);
}
export const MetricsAlertDropdown = () => {
const [popoverOpen, setPopoverOpen] = useState(false);
const [visibleFlyoutType, setVisibleFlyoutType] = useState<VisibleFlyoutType | null>(null);
@ -34,8 +145,6 @@ export const MetricsAlertDropdown = () => {
() => Boolean(uiCapabilities?.infrastructure?.save),
[uiCapabilities]
);
const { featureFlags } = usePluginConfig();
const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]);
const closePopover = useCallback(() => {
@ -45,49 +154,18 @@ export const MetricsAlertDropdown = () => {
const togglePopover = useCallback(() => {
setPopoverOpen(!popoverOpen);
}, [setPopoverOpen, popoverOpen]);
const infrastructureAlertsPanel = useMemo(
() => ({
id: 1,
title: i18n.translate('xpack.infra.alerting.infrastructureDropdownTitle', {
defaultMessage: 'Infrastructure rules',
}),
items: [
{
'data-test-subj': 'inventory-alerts-create-rule',
name: i18n.translate('xpack.infra.alerting.createInventoryRuleButton', {
defaultMessage: 'Create inventory rule',
}),
onClick: () => {
closePopover();
setVisibleFlyoutType('inventory');
},
},
],
}),
[setVisibleFlyoutType, closePopover]
const onCreateRuleClick = useCallback(
(flyoutType: VisibleFlyoutType) => {
closePopover();
setVisibleFlyoutType(flyoutType);
},
[closePopover]
);
const metricsAlertsPanel = useMemo(
() => ({
id: 2,
title: i18n.translate('xpack.infra.alerting.metricsDropdownTitle', {
defaultMessage: 'Metrics rules',
}),
items: [
{
'data-test-subj': 'metrics-threshold-alerts-create-rule',
name: i18n.translate('xpack.infra.alerting.createThresholdRuleButton', {
defaultMessage: 'Create threshold rule',
}),
onClick: () => {
closePopover();
setVisibleFlyoutType('metricThreshold');
},
},
],
}),
[setVisibleFlyoutType, closePopover]
);
const infrastructureMenu = useInfrastructureMenu(onCreateRuleClick);
const metricsMenu = useMetricsMenu(onCreateRuleClick);
const customThresholdMenu = useCustomThresholdMenu(onCreateRuleClick);
const manageRulesLinkProps = observability.useRulesLink();
@ -102,56 +180,27 @@ export const MetricsAlertDropdown = () => {
[manageRulesLinkProps]
);
const firstPanelMenuItems: EuiContextMenuPanelDescriptor['items'] = useMemo(
() =>
canCreateAlerts
? [
{
'data-test-subj': 'inventory-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.infrastructureDropdownMenu', {
defaultMessage: 'Infrastructure',
}),
panel: 1,
},
{
'data-test-subj': 'metrics-threshold-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.metricsDropdownMenu', {
defaultMessage: 'Metrics',
}),
panel: 2,
},
...(featureFlags.customThresholdAlertsEnabled
? [
{
'data-test-subj': 'custom-threshold-alerts-menu-option',
name: i18n.translate('xpack.infra.alerting.customThresholdDropdownMenu', {
defaultMessage: 'Create custom threshold rule',
}),
onClick: () => {
closePopover();
setVisibleFlyoutType('customThreshold');
},
},
]
: []),
manageAlertsMenuItem,
]
: [manageAlertsMenuItem],
[canCreateAlerts, closePopover, featureFlags.customThresholdAlertsEnabled, manageAlertsMenuItem]
);
const panels: EuiContextMenuPanelDescriptor[] = useMemo(
() =>
[
{
id: 0,
title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', {
defaultMessage: 'Alerts and rules',
}),
items: firstPanelMenuItems,
},
].concat(canCreateAlerts ? [infrastructureAlertsPanel, metricsAlertsPanel] : []),
[infrastructureAlertsPanel, metricsAlertsPanel, firstPanelMenuItems, canCreateAlerts]
() => [
{
id: 0,
title: i18n.translate('xpack.infra.alerting.alertDropdownTitle', {
defaultMessage: 'Alerts and rules',
}),
items: canCreateAlerts
? [
...infrastructureMenu.items,
...metricsMenu.items,
...customThresholdMenu.items,
manageAlertsMenuItem,
]
: [manageAlertsMenuItem],
},
...(canCreateAlerts
? [...infrastructureMenu.panels, ...metricsMenu.panels, ...customThresholdMenu.panels]
: []),
],
[canCreateAlerts, infrastructureMenu, metricsMenu, customThresholdMenu, manageAlertsMenuItem]
);
return (

View file

@ -27,6 +27,15 @@ export function AlertFlyout({ onClose }: Props) {
onClose,
canChangeTrigger: false,
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
metadata: {
currentOptions: {
/*
Setting the groupBy is currently required in custom threshold
rule for it to populate the rule with additional host context.
*/
groupBy: 'host.name',
},
},
});
}, [onClose, triggersActionsUI]);

View file

@ -1,53 +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, { useCallback, useContext, useMemo } from 'react';
import { TriggerActionsContext } from '../../../utils/triggers_actions_context';
import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
import { InfraWaffleMapOptions } from '../../../lib/lib';
import { InventoryItemType } from '../../../../common/inventory_models/types';
import { useAlertPrefillContext } from '../../use_alert_prefill';
interface Props {
visible?: boolean;
metric?: InfraWaffleMapOptions['metric'];
nodeType?: InventoryItemType;
filter?: string;
setVisible(val: boolean): void;
}
export const AlertFlyout = ({ metric, nodeType, visible, setVisible }: Props) => {
const { triggersActionsUI } = useContext(TriggerActionsContext);
const onCloseFlyout = useCallback(() => setVisible(false), [setVisible]);
const AddAlertFlyout = useMemo(
() =>
triggersActionsUI &&
triggersActionsUI.getAddRuleFlyout({
consumer: 'infrastructure',
onClose: onCloseFlyout,
canChangeTrigger: false,
ruleTypeId: 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 <AlertFlyout metric={metric} nodeType={nodeType} visible setVisible={onClose} />;
};

View file

@ -1,86 +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-helpers';
// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock`
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
import React from 'react';
import { Expression, AlertContextMeta } from './expression';
import { act } from 'react-dom/test-utils';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
jest.mock('../../../containers/metrics_source/source', () => ({
withSourceProvider: () => jest.fn,
useSourceContext: () => ({
source: { id: 'default' },
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
}),
}));
jest.mock('../../../hooks/use_kibana', () => ({
useKibanaContextForPlugin: () => ({
services: mockCoreMock.createStart(),
}),
}));
jest.mock('../../../hooks/use_kibana_space', () => ({
useActiveKibanaSpace: () => ({
space: { id: 'default' },
}),
}));
jest.mock('../../../containers/ml/infra_ml_capabilities', () => ({
useInfraMLCapabilities: () => ({
isLoading: false,
hasInfraMLCapabilities: true,
}),
}));
const dataViewMock = dataViewPluginMocks.createStartContract();
describe('Expression', () => {
async function setup(currentOptions: AlertContextMeta) {
const ruleParams = {
metric: undefined,
nodeType: undefined,
threshold: 50,
};
const wrapper = mountWithIntl(
<Expression
ruleInterval="1m"
ruleThrottle="1m"
alertNotifyWhen="onThrottleInterval"
ruleParams={ruleParams as any}
errors={{}}
setRuleParams={(key, value) => Reflect.set(ruleParams, key, value)}
setRuleProperty={() => {}}
metadata={currentOptions}
dataViews={dataViewMock}
/>
);
const update = async () =>
await act(async () => {
await nextTick();
wrapper.update();
});
await update();
return { wrapper, update, ruleParams };
}
it('should prefill the alert using the context metadata', async () => {
const currentOptions = {
nodeType: 'pod',
metric: { type: 'tx' },
};
const { ruleParams } = await setup(currentOptions as AlertContextMeta);
expect(ruleParams.nodeType).toBe('k8s');
expect(ruleParams.metric).toBe('network_out');
});
});

View file

@ -1,298 +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 { EuiFlexGroup, EuiSkeletonText, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { euiStyled, EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import {
RuleTypeParams,
RuleTypeParamsExpressionProps,
WhenExpression,
} from '@kbn/triggers-actions-ui-plugin/public';
import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
import { useSourceContext, withSourceProvider } from '../../../containers/metrics_source';
import { MetricAnomalyParams } from '../../../../common/alerting/metrics';
import { findInventoryModel } from '../../../../common/inventory_models';
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
import { SubscriptionSplashPrompt } from '../../../components/subscription_splash_content';
import { useInfraMLCapabilities } from '../../../containers/ml/infra_ml_capabilities';
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
import { InfraWaffleMapOptions } from '../../../lib/lib';
import { InfluencerFilter } from './influencer_filter';
import { NodeTypeExpression } from './node_type';
import { SeverityThresholdExpression } from './severity_threshold';
export interface AlertContextMeta {
metric?: InfraWaffleMapOptions['metric'];
nodeType?: InventoryItemType;
}
type AlertParams = RuleTypeParams &
MetricAnomalyParams & { sourceId: string; spaceId: string; hasInfraMLCapabilities: boolean };
type Props = Omit<
RuleTypeParamsExpressionProps<AlertParams, AlertContextMeta>,
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' | 'onChangeMetaData'
>;
export const defaultExpression = {
metric: 'memory_usage' as MetricAnomalyParams['metric'],
threshold: ML_ANOMALY_THRESHOLD.MAJOR as MetricAnomalyParams['threshold'],
nodeType: 'hosts' as MetricAnomalyParams['nodeType'],
influencerFilter: undefined,
};
export const Expression: React.FC<Props> = (props) => {
const { hasInfraMLCapabilities, isLoading: isLoadingMLCapabilities } = useInfraMLCapabilities();
const { space } = useActiveKibanaSpace();
const { setRuleParams, ruleParams, ruleInterval, metadata } = props;
const { source, createDerivedIndexPattern } = useSourceContext();
const derivedIndexPattern = useMemo(
() => createDerivedIndexPattern(),
[createDerivedIndexPattern]
);
const [influencerFieldName, updateInfluencerFieldName] = useState(
ruleParams.influencerFilter?.fieldName ?? 'host.name'
);
useEffect(() => {
setRuleParams('hasInfraMLCapabilities', hasInfraMLCapabilities);
}, [setRuleParams, hasInfraMLCapabilities]);
useEffect(() => {
if (ruleParams.influencerFilter) {
setRuleParams('influencerFilter', {
...ruleParams.influencerFilter,
fieldName: influencerFieldName,
});
}
}, [influencerFieldName, ruleParams, setRuleParams]);
const updateInfluencerFieldValue = useCallback(
(value: string) => {
if (value) {
setRuleParams('influencerFilter', {
...ruleParams.influencerFilter,
fieldValue: value,
} as MetricAnomalyParams['influencerFilter']);
} else {
setRuleParams('influencerFilter', undefined);
}
},
[setRuleParams, ruleParams]
);
useEffect(() => {
setRuleParams('alertInterval', ruleInterval);
}, [setRuleParams, ruleInterval]);
const updateNodeType = useCallback(
(nt: any) => {
setRuleParams('nodeType', nt);
},
[setRuleParams]
);
const updateMetric = useCallback(
(metric: string) => {
setRuleParams('metric', metric as MetricAnomalyParams['metric']);
},
[setRuleParams]
);
const updateSeverityThreshold = useCallback(
(threshold: any) => {
setRuleParams('threshold', threshold);
},
[setRuleParams]
);
const prefillNodeType = useCallback(() => {
const md = metadata;
if (md && md.nodeType) {
setRuleParams(
'nodeType',
getMLNodeTypeFromInventoryNodeType(md.nodeType) ?? defaultExpression.nodeType
);
} else {
setRuleParams('nodeType', defaultExpression.nodeType);
}
}, [metadata, setRuleParams]);
const prefillMetric = useCallback(() => {
const md = metadata;
if (md && md.metric) {
setRuleParams(
'metric',
getMLMetricFromInventoryMetric(md.metric.type) ?? defaultExpression.metric
);
} else {
setRuleParams('metric', defaultExpression.metric);
}
}, [metadata, setRuleParams]);
useEffect(() => {
if (!ruleParams.nodeType) {
prefillNodeType();
}
if (!ruleParams.threshold) {
setRuleParams('threshold', defaultExpression.threshold);
}
if (!ruleParams.metric) {
prefillMetric();
}
if (!ruleParams.sourceId) {
setRuleParams('sourceId', source?.id || 'default');
}
if (!ruleParams.spaceId) {
setRuleParams('spaceId', space?.id || 'default');
}
}, [metadata, derivedIndexPattern, defaultExpression, source, space]); // eslint-disable-line react-hooks/exhaustive-deps
if (isLoadingMLCapabilities) return <EuiSkeletonText lines={10} />;
if (!hasInfraMLCapabilities) return <SubscriptionSplashPrompt />;
return (
// https://github.com/elastic/kibana/issues/89506
<EuiThemeProvider>
<EuiText size="xs">
<h4>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.conditions"
defaultMessage="Conditions"
/>
</h4>
</EuiText>
<StyledExpression>
<StyledExpressionRow>
<NodeTypeExpression
options={nodeTypes}
value={ruleParams.nodeType ?? defaultExpression.nodeType}
onChange={updateNodeType}
/>
</StyledExpressionRow>
</StyledExpression>
<EuiSpacer size={'xs'} />
<StyledExpressionRow>
<StyledExpression>
<WhenExpression
aggType={ruleParams.metric ?? defaultExpression.metric}
onChangeSelectedAggType={updateMetric}
customAggTypesOptions={{
memory_usage: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage', {
defaultMessage: 'Memory usage',
}),
fieldRequired: false,
value: 'memory_usage',
validNormalizedTypes: [],
},
network_in: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn', {
defaultMessage: 'Network in',
}),
fieldRequired: false,
validNormalizedTypes: [],
value: 'network_in',
},
network_out: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut', {
defaultMessage: 'Network out',
}),
fieldRequired: false,
validNormalizedTypes: [],
value: 'network_out',
},
}}
/>
</StyledExpression>
<StyledExpression>
<SeverityThresholdExpression
value={ruleParams.threshold ?? ML_ANOMALY_THRESHOLD.CRITICAL}
onChange={updateSeverityThreshold}
/>
</StyledExpression>
</StyledExpressionRow>
<EuiSpacer size={'m'} />
<InfluencerFilter
derivedIndexPattern={derivedIndexPattern}
nodeType={ruleParams.nodeType}
fieldName={influencerFieldName}
fieldValue={ruleParams.influencerFilter?.fieldValue ?? ''}
onChangeFieldName={updateInfluencerFieldName}
onChangeFieldValue={updateInfluencerFieldValue}
/>
<EuiSpacer size={'m'} />
</EuiThemeProvider>
);
};
// required for dynamic import
// eslint-disable-next-line import/no-default-export
export default withSourceProvider<Props>(Expression)('default');
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
) => MetricAnomalyParams['metric'] | null = (metric) => {
switch (metric) {
case 'memory':
return 'memory_usage';
case 'tx':
return 'network_out';
case 'rx':
return 'network_in';
default:
return null;
}
};
const getMLNodeTypeFromInventoryNodeType: (
nodeType: InventoryItemType
) => MetricAnomalyParams['nodeType'] | null = (nodeType) => {
switch (nodeType) {
case 'host':
return 'hosts';
case 'pod':
return 'k8s';
default:
return null;
}
};

View file

@ -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<typeof MetricsExplorerKueryBar>[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 (
<EuiFormRow
label={
<EuiCheckbox
label={filterByNodeLabel}
id="anomalyAlertFilterByNodeCheckbox"
onChange={toggleEnabled}
checked={isEnabled}
/>
}
helpText={
isEnabled ? (
<>
{i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpText', {
defaultMessage:
'Limit the scope of your alert trigger to anomalies influenced by certain node(s).',
})}
<br />
{i18n.translate('xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample', {
defaultMessage: 'For example: "my-node-1" or "my-node-*"',
})}
</>
) : null
}
fullWidth
display="rowCompressed"
>
{isEnabled ? (
<EuiFlexGroup>
<EuiFlexItem>
<EuiSelect
data-test-subj="infraInfluencerFilterSelect"
id="selectInfluencerFieldName"
value={fieldName}
onChange={onSelectFieldName}
options={fieldNameOptions}
/>
</EuiFlexItem>
<EuiFlexItem>
<MetricsExplorerKueryBar
derivedIndexPattern={derivedIndexPattern}
onChange={debouncedOnUpdateFieldValue}
onSubmit={onUpdateFieldValue}
value={storedFieldValue}
curryLoadSuggestions={affixFieldNameToQuery}
placeholder={i18n.translate(
'xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder',
{
defaultMessage: 'Everything',
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<></>
)}
</EuiFormRow>
);
};
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',
});

View file

@ -1,118 +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 (
<EuiPopover
button={
<EuiExpression
data-test-subj="nodeTypeExpression"
description={i18n.translate(
'xpack.infra.metrics.alertFlyout.expression.for.descriptionLabel',
{
defaultMessage: 'For',
}
)}
value={options[value].text}
isActive={aggTypePopoverOpen}
onClick={() => {
setAggTypePopoverOpen(true);
}}
/>
}
isOpen={aggTypePopoverOpen}
closePopover={() => {
setAggTypePopoverOpen(false);
}}
ownFocus
anchorPosition={popupPosition ?? 'downLeft'}
>
<div>
<ClosablePopoverTitle onClose={() => setAggTypePopoverOpen(false)}>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.expression.for.popoverTitle"
defaultMessage="Node Type"
/>
</ClosablePopoverTitle>
<EuiSelect
data-test-subj="forExpressionSelect"
value={value}
fullWidth
onChange={(e) => {
onChange(e.target.value as Node);
setAggTypePopoverOpen(false);
}}
options={Object.values(options).map((o) => o)}
/>
</div>
</EuiPopover>
);
};
interface ClosablePopoverTitleProps {
children: JSX.Element;
onClose: () => void;
}
export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
return (
<EuiPopoverTitle>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem>{children}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
data-test-subj="infraClosablePopoverTitleButton"
iconType="cross"
color="danger"
aria-label={i18n.translate(
'xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel',
{
defaultMessage: 'Close',
}
)}
onClick={() => onClose()}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
);
};

View file

@ -1,141 +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 { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
interface WhenExpressionProps {
value: Exclude<ML_ANOMALY_THRESHOLD, ML_ANOMALY_THRESHOLD.LOW>;
onChange: (value: ML_ANOMALY_THRESHOLD) => void;
popupPosition?:
| 'upCenter'
| 'upLeft'
| 'upRight'
| 'downCenter'
| 'downLeft'
| 'downRight'
| 'leftCenter'
| 'leftUp'
| 'leftDown'
| 'rightCenter'
| 'rightUp'
| 'rightDown';
}
const options = {
[ML_ANOMALY_THRESHOLD.CRITICAL]: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel', {
defaultMessage: 'Critical',
}),
value: ML_ANOMALY_THRESHOLD.CRITICAL,
},
[ML_ANOMALY_THRESHOLD.MAJOR]: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel', {
defaultMessage: 'Major',
}),
value: ML_ANOMALY_THRESHOLD.MAJOR,
},
[ML_ANOMALY_THRESHOLD.MINOR]: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel', {
defaultMessage: 'Minor',
}),
value: ML_ANOMALY_THRESHOLD.MINOR,
},
[ML_ANOMALY_THRESHOLD.WARNING]: {
text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel', {
defaultMessage: 'Warning',
}),
value: ML_ANOMALY_THRESHOLD.WARNING,
},
};
export const SeverityThresholdExpression = ({
value,
onChange,
popupPosition,
}: WhenExpressionProps) => {
const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false);
return (
<EuiPopover
button={
<EuiExpression
data-test-subj="nodeTypeExpression"
description={i18n.translate(
'xpack.infra.metrics.alertFlyout.expression.severityScore.descriptionLabel',
{
defaultMessage: 'Severity score is above',
}
)}
value={options[value].text}
isActive={aggTypePopoverOpen}
onClick={() => {
setAggTypePopoverOpen(true);
}}
/>
}
isOpen={aggTypePopoverOpen}
closePopover={() => {
setAggTypePopoverOpen(false);
}}
ownFocus
anchorPosition={popupPosition ?? 'downLeft'}
>
<div>
<ClosablePopoverTitle onClose={() => setAggTypePopoverOpen(false)}>
<FormattedMessage
id="xpack.infra.metrics.alertFlyout.expression.severityScore.popoverTitle"
defaultMessage="Severity Score"
/>
</ClosablePopoverTitle>
<EuiSelect
data-test-subj="severityExpressionSelect"
value={value}
fullWidth
onChange={(e) => {
onChange(Number(e.target.value) as ML_ANOMALY_THRESHOLD);
setAggTypePopoverOpen(false);
}}
options={Object.values(options).map((o) => o)}
/>
</div>
</EuiPopover>
);
};
interface ClosablePopoverTitleProps {
children: JSX.Element;
onClose: () => void;
}
export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
return (
<EuiPopoverTitle>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem>{children}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
data-test-subj="infraClosablePopoverTitleButton"
iconType="cross"
color="danger"
aria-label={i18n.translate(
'xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel',
{
defaultMessage: 'Close',
}
)}
onClick={() => onClose()}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
);
};

View file

@ -1,34 +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 type { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public';
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;
}

View file

@ -1,45 +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 { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public';
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { METRIC_ANOMALY_ALERT_TYPE_ID } from '../../../common/alerting/metrics';
import { validateMetricAnomaly } from './components/validation';
interface MetricAnomalyRuleTypeParams extends RuleTypeParams {
hasInfraMLCapabilities: boolean;
}
export function createMetricAnomalyRuleType(): RuleTypeModel<MetricAnomalyRuleTypeParams> {
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}/infrastructure-anomaly-alert.html`;
},
ruleParamsExpression: 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,
};
}

View file

@ -9,6 +9,7 @@ import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { useSummaryTimeRange } from '@kbn/observability-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { usePluginConfig } from '../../../../containers/plugin_config_context';
import type { AlertsEsQuery } from '../../../../common/alerts/types';
import type { InventoryItemType } from '../../../../../common/inventory_models/types';
import { findInventoryFields } from '../../../../../common/inventory_models';
@ -32,6 +33,7 @@ export const AlertsSummaryContent = ({
assetType: InventoryItemType;
dateRange: TimeRange;
}) => {
const { featureFlags } = usePluginConfig();
const [isAlertFlyoutVisible, { toggle: toggleAlertFlyout }] = useBoolean(false);
const { overrides } = useAssetDetailsRenderPropsContext();
@ -51,9 +53,11 @@ export const AlertsSummaryContent = ({
<EuiFlexItem>
<AlertsSectionTitle />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LinkToAlertsRule onClick={toggleAlertFlyout} />
</EuiFlexItem>
{featureFlags.inventoryThresholdAlertRuleEnabled && (
<EuiFlexItem grow={false}>
<LinkToAlertsRule onClick={toggleAlertFlyout} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<LinkToAlertsPage
assetName={assetName}
@ -64,13 +68,16 @@ export const AlertsSummaryContent = ({
</EuiFlexGroup>
<EuiSpacer size="s" />
<MemoAlertSummaryWidget alertsQuery={alertsEsQueryByStatus} dateRange={dateRange} />
<AlertFlyout
filter={`${findInventoryFields(assetType).name}: "${assetName}"`}
nodeType={assetType}
setVisible={toggleAlertFlyout}
visible={isAlertFlyoutVisible}
options={overrides?.alertRule?.options}
/>
{featureFlags.inventoryThresholdAlertRuleEnabled && (
<AlertFlyout
filter={`${findInventoryFields(assetType).name}: "${assetName}"`}
nodeType={assetType}
setVisible={toggleAlertFlyout}
visible={isAlertFlyoutVisible}
options={overrides?.alertRule?.options}
/>
)}
</>
);
};

View file

@ -24,6 +24,9 @@ describe('usePluginConfig()', () => {
logsUIEnabled: false,
metricsExplorerEnabled: false,
osqueryEnabled: false,
inventoryThresholdAlertRuleEnabled: true,
metricThresholdAlertRuleEnabled: true,
logThresholdAlertRuleEnabled: true,
},
};
const { result } = renderHook(() => usePluginConfig(), {

View file

@ -11,6 +11,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration';
import type { InfraConfig } from '../../../../common/plugin_config_types';
import {
Comparator,
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
@ -81,10 +82,15 @@ const groupActionVariableDescription = i18n.translate(
}
);
export async function registerMetricInventoryThresholdRuleType(
export async function registerInventoryThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
libs: InfraBackendLibs,
{ featureFlags }: InfraConfig
) {
if (!featureFlags.inventoryThresholdAlertRuleEnabled) {
return;
}
alertingPlugin.registerType({
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.metrics.inventory.alertName', {

View file

@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import type { InfraConfig } from '../../../../common/plugin_config_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import { extractReferences, injectReferences } from './log_threshold_references_manager';
@ -103,8 +104,13 @@ const viewInAppUrlActionVariableDescription = i18n.translate(
export async function registerLogThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
libs: InfraBackendLibs,
{ featureFlags }: InfraConfig
) {
if (!featureFlags.logThresholdAlertRuleEnabled) {
return;
}
if (!alertingPlugin) {
throw new Error(
'Cannot register log threshold alert type. Both the actions and alerting plugins need to be enabled.'

View file

@ -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<MetricAnomalyParams, 'alertInterval'> & {
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({
context: {
spaceId,
mlSystem,
mlAnomalyDetectors,
},
sourceId: sourceId ?? 'default',
anomalyThreshold: threshold,
startTime,
endTime,
metric,
sort: { field: 'anomalyScore', direction: 'desc' },
pagination: { pageSize: 100 },
influencerFilter,
});
return result;
};

View file

@ -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 { KibanaRequest } from '@kbn/core/server';
import { first } from 'lodash';
import moment from 'moment';
import {
ActionGroup,
AlertInstanceContext as AlertContext,
AlertInstanceState as AlertState,
} from '@kbn/alerting-plugin/common';
import { RuleExecutorOptions } from '@kbn/alerting-plugin/server';
import { MlPluginSetup } from '@kbn/ml-plugin/server';
import { AlertStates, MetricAnomalyParams } from '../../../../common/alerting/metrics';
import { getIntervalInSeconds } from '../../../../common/utils/get_interval_in_seconds';
import { MappedAnomalyHit } from '../../infra_ml';
import { InfraBackendLibs } from '../../infra_types';
import { stateToAlertMessage } from '../common/messages';
import { evaluateCondition } from './evaluate_condition';
import { MetricAnomalyAllowedActionGroups } from './register_metric_anomaly_rule_type';
export const createMetricAnomalyExecutor =
(_libs: InfraBackendLibs, ml?: MlPluginSetup) =>
async ({
services,
params,
startedAt,
}: RuleExecutorOptions<
/**
* TODO: Remove this use of `any` by utilizing a proper type
*/
Record<string, any>,
Record<string, any>,
AlertState,
AlertContext,
MetricAnomalyAllowedActionGroups
>) => {
if (!ml) {
return { state: {} };
}
const request = {} as KibanaRequest;
const mlSystem = ml.mlSystemProvider(request, services.savedObjectsClient);
const mlAnomalyDetectors = ml.anomalyDetectorsProvider(request, services.savedObjectsClient);
const { metric, alertInterval, influencerFilter, sourceId, spaceId, nodeType, threshold } =
params as MetricAnomalyParams;
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: 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[])!;
const alert = services.alertFactory.create(`${nodeType}-${metric}`);
alert.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(', '),
});
}
return { state: {} };
};
export const FIRED_ACTIONS_ID = 'metrics.anomaly.fired';
export const FIRED_ACTIONS: ActionGroup<typeof FIRED_ACTIONS_ID> = {
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',
}),
};

View file

@ -1,132 +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 '@kbn/datemath';
import { countBy } from 'lodash';
import {
isTooManyBucketsPreviewException,
MetricAnomalyParams,
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
} from '../../../../common/alerting/metrics';
import { getIntervalInSeconds } from '../../../../common/utils/get_interval_in_seconds';
import { MlAnomalyDetectors, MlSystem } from '../../../types';
import { MappedAnomalyHit } from '../../infra_ml';
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;
alertNotifyWhen: string;
}
export const previewMetricAnomalyAlert = async ({
mlSystem,
mlAnomalyDetectors,
spaceId,
params,
sourceId,
lookback,
alertInterval,
alertThrottle,
alertNotifyWhen,
}: PreviewMetricAnomalyAlertParams) => {
const { metric, threshold, influencerFilter, nodeType } = params as MetricAnomalyParams;
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
const throttleIntervalInSeconds = getIntervalInSeconds(alertThrottle);
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;
let previousActionGroup: string | null = null;
const notifyWithThrottle = (actionGroup: string) => {
if (alertNotifyWhen === 'onActionGroupChange') {
if (previousActionGroup !== actionGroup) numberOfNotifications++;
} else if (alertNotifyWhen === 'onThrottleInterval') {
if (throttleTracker === 0) numberOfNotifications++;
throttleTracker += alertIntervalInSeconds;
} else {
numberOfNotifications++;
}
previousActionGroup = actionGroup;
};
// 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('fired');
} else {
previousActionGroup = 'recovered';
if (throttleTracker > 0) {
throttleTracker += alertIntervalInSeconds;
}
}
if (throttleTracker >= throttleIntervalInSeconds) {
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}`);
}
};

View file

@ -1,125 +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 { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { MlPluginSetup } from '@kbn/ml-plugin/server';
import {
RuleType,
AlertInstanceState as AlertState,
AlertInstanceContext as AlertContext,
GetViewInAppRelativeUrlFnOpts,
} from '@kbn/alerting-plugin/server';
import { RecoveredActionGroupId } from '@kbn/alerting-plugin/common';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
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';
export type MetricAnomalyAllowedActionGroups = typeof FIRED_ACTIONS_ID;
export const registerMetricAnomalyRuleType = (
libs: InfraBackendLibs,
ml?: MlPluginSetup
): RuleType<
/**
* TODO: Remove this use of `any` by utilizing a proper type
*/
Record<string, any>,
never, // Only use if defining useSavedObjectReferences hook
Record<string, any>,
AlertState,
AlertContext,
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(),
spaceId: schema.string(),
},
{ unknowns: 'allow' }
),
},
defaultActionGroupId: FIRED_ACTIONS_ID,
actionGroups: [FIRED_ACTIONS],
category: DEFAULT_APP_CATEGORIES.observability.id,
producer: 'infrastructure',
minimumLicenseRequired: 'basic',
isExportable: true,
executor: createMetricAnomalyExecutor(libs, ml),
fieldsForAAD: O11Y_AAD_FIELDS,
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.',
}),
},
],
},
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -1903,6 +1903,9 @@ const createMockStaticConfiguration = (sources: any): InfraConfig => ({
logsUIEnabled: true,
metricsExplorerEnabled: true,
osqueryEnabled: true,
inventoryThresholdAlertRuleEnabled: true,
metricThresholdAlertRuleEnabled: true,
logThresholdAlertRuleEnabled: true,
},
enabled: true,
sources,

View file

@ -15,6 +15,7 @@ import {
RuleType,
} from '@kbn/alerting-plugin/server';
import { observabilityPaths } from '@kbn/observability-plugin/common';
import type { InfraConfig } from '../../../../common/plugin_config_types';
import { Comparator, METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics';
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api';
import { InfraBackendLibs } from '../../infra_types';
@ -56,8 +57,13 @@ export type MetricThresholdAlertType = Omit<RuleType, 'ActionGroupIdsOf'> & {
export async function registerMetricThresholdRuleType(
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs
libs: InfraBackendLibs,
{ featureFlags }: InfraConfig
) {
if (!featureFlags.metricThresholdAlertRuleEnabled) {
return;
}
const baseCriterion = {
threshold: schema.arrayOf(schema.number()),
comparator: oneOfLiterals(Object.values(Comparator)),

View file

@ -7,12 +7,11 @@
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
import { type IRuleTypeAlerts, PluginSetupContract } from '@kbn/alerting-plugin/server';
import { MlPluginSetup } from '@kbn/ml-plugin/server';
import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type';
import { registerMetricInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type';
import { registerMetricAnomalyRuleType } from './metric_anomaly/register_metric_anomaly_rule_type';
import { registerInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type';
import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type';
import { InfraBackendLibs } from '../infra_types';
import type { InfraConfig } from '../../types';
export const LOGS_RULES_ALERT_CONTEXT = 'observability.logs';
// Defines which alerts-as-data index logs rules will use
@ -35,18 +34,16 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = {
const registerRuleTypes = (
alertingPlugin: PluginSetupContract,
libs: InfraBackendLibs,
ml?: MlPluginSetup
config: InfraConfig
) => {
if (alertingPlugin) {
alertingPlugin.registerType(registerMetricAnomalyRuleType(libs, ml));
const registerFns = [
registerLogThresholdRuleType,
registerMetricInventoryThresholdRuleType,
registerInventoryThresholdRuleType,
registerMetricThresholdRuleType,
];
registerFns.forEach((fn) => {
fn(alertingPlugin, libs);
fn(alertingPlugin, libs, config);
});
}
};

View file

@ -130,6 +130,9 @@ const createMockStaticConfiguration = (sources: any): InfraConfig => ({
logsUIEnabled: true,
metricsExplorerEnabled: true,
osqueryEnabled: true,
inventoryThresholdAlertRuleEnabled: true,
metricThresholdAlertRuleEnabled: true,
logThresholdAlertRuleEnabled: true,
},
sources,
enabled: true,

View file

@ -83,7 +83,7 @@ export const config: PluginConfigDescriptor<InfraConfig> = {
featureFlags: schema.object({
customThresholdAlertsEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: false }),
serverless: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
logsUIEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: true }),
@ -97,6 +97,18 @@ export const config: PluginConfigDescriptor<InfraConfig> = {
traditional: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
inventoryThresholdAlertRuleEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
metricThresholdAlertRuleEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
logThresholdAlertRuleEnabled: offeringBasedSchema({
traditional: schema.boolean({ defaultValue: true }),
serverless: schema.boolean({ defaultValue: false }),
}),
}),
}),
deprecations: configDeprecations,
@ -238,7 +250,7 @@ export class InfraServerPlugin
}
initInfraServer(this.libs);
registerRuleTypes(plugins.alerting, this.libs, plugins.ml);
registerRuleTypes(plugins.alerting, this.libs, this.config);
core.http.registerRouteHandlerContext<InfraPluginRequestHandlerContext, 'infra'>(
'infra',

View file

@ -16,8 +16,7 @@ export const initGetLogAlertsChartPreviewDataRoute = ({
framework,
getStartServices,
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
// Replace with the corresponding logs alert rule feature flag
if (!framework.config.featureFlags.logsUIEnabled) {
if (!framework.config.featureFlags.logThresholdAlertRuleEnabled) {
return;
}

View file

@ -166,7 +166,6 @@ export enum MetricsExplorerChartType {
export enum InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
Anomaly = 'metrics.alert.anomaly',
}
export enum AlertStates {

View file

@ -14,7 +14,6 @@ import { TimeUnitChar } from '../../../../common';
export enum InfraRuleType {
MetricThreshold = 'metrics.alert.threshold',
InventoryThreshold = 'metrics.alert.inventory.threshold',
Anomaly = 'metrics.alert.anomaly',
}
export enum AlertStates {

View file

@ -18469,8 +18469,6 @@
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "Champ {name}",
"xpack.infra.metrics.alertFlyout.customEquationEditor.filterLabel": "Filtre KQL {name}",
"xpack.infra.metrics.alertFlyout.ofExpression.helpTextDetail": "Vous ne trouvez pas un indicateur ? {documentationLink}.",
"xpack.infra.metrics.alerting.anomaly.summaryHigher": "{differential}x plus élevé",
"xpack.infra.metrics.alerting.anomaly.summaryLower": "{differential}x plus bas",
"xpack.infra.metrics.alerting.threshold.errorAlertReason": "Elasticsearch a échoué lors de l'interrogation des données pour {metric}",
"xpack.infra.metrics.alerting.threshold.firedAlertReason": "{metric} est {currentValue} dans les dernières {duration}{group}. Alerte lorsque {comparator} {threshold}.",
"xpack.infra.metrics.alerting.threshold.noDataAlertReason": "{metric} n'a signalé aucune donnée dans les dernières {interval} {group}",
@ -19051,12 +19049,6 @@
"xpack.infra.metrics.alertFlyout.alertOnGroupDisappear": "Me prévenir si un groupe cesse de signaler les données",
"xpack.infra.metrics.alertFlyout.alertOnNoData": "M'alerter s'il n'y a aucune donnée",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError.docsLink": "les documents",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpText": "Limitez la portée de votre déclenchement d'alerte aux anomalies influencées par certains nœuds.",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample": "Par exemple : \"my-node-1\" ou \"my-node-*\"",
"xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder": "Tout",
"xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage": "Utilisation mémoire",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn": "Entrée réseau",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut": "Sortie réseau",
"xpack.infra.metrics.alertFlyout.conditions": "Conditions",
"xpack.infra.metrics.alertFlyout.createAlertPerHelpText": "Créer une alerte pour chaque valeur unique. Par exemple : \"host.id\" ou \"cloud.region\".",
"xpack.infra.metrics.alertFlyout.createAlertPerText": "Regrouper les alertes par (facultatif)",
@ -19076,7 +19068,6 @@
"xpack.infra.metrics.alertFlyout.error.equation.invalidCharacters": "Le champ d'équation prend en charge uniquement les caractères suivants : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =",
"xpack.infra.metrics.alertFlyout.error.invalidFilterQuery": "La requête de filtre n'est pas valide.",
"xpack.infra.metrics.alertFlyout.error.metricRequired": "L'indicateur est requis.",
"xpack.infra.metrics.alertFlyout.error.mlCapabilitiesRequired": "Impossible de créer une alerte d'anomalie lors que le Machine Learning est désactivé.",
"xpack.infra.metrics.alertFlyout.error.thresholdRequired": "Le seuil est requis.",
"xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired": "Les seuils doivent contenir un nombre valide.",
"xpack.infra.metrics.alertFlyout.error.timeRequred": "La taille de temps est requise.",
@ -19086,13 +19077,6 @@
"xpack.infra.metrics.alertFlyout.expression.metric.popoverTitle": "Indicateur",
"xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel": "Sélectionner un indicateur",
"xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "Quand",
"xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel": "Critique",
"xpack.infra.metrics.alertFlyout.expression.severityScore.descriptionLabel": "Le score de sévérité est supérieur à",
"xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel": "Majeur",
"xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel": "Mineur",
"xpack.infra.metrics.alertFlyout.expression.severityScore.popoverTitle": "Score de sévérité",
"xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel": "Avertissement",
"xpack.infra.metrics.alertFlyout.filterByNodeLabel": "Filtrer par nœud",
"xpack.infra.metrics.alertFlyout.filterHelpText": "Utilisez une expression KQL pour limiter la portée de votre déclenchement d'alerte.",
"xpack.infra.metrics.alertFlyout.filterLabel": "Filtre (facultatif)",
"xpack.infra.metrics.alertFlyout.groupDisappearHelpText": "Activez cette option pour déclencher laction si un groupe précédemment détecté cesse de signaler des résultats. Ce nest pas recommandé pour les infrastructures à montée en charge dynamique qui peuvent rapidement lancer ou stopper des nœuds automatiquement.",
@ -19104,18 +19088,6 @@
"xpack.infra.metrics.alertFlyout.warningThreshold": "Avertissement",
"xpack.infra.metrics.alerting.alertDetailUrlActionVariableDescription": "Lien vers laffichage de résolution des problèmes dalerte pour voir plus de contextes et de détails. La chaîne sera vide si server.publicBaseUrl n'est pas configuré.",
"xpack.infra.metrics.alerting.alertStateActionVariableDescription": "État actuel de l'alerte",
"xpack.infra.metrics.alerting.anomaly.defaultActionMessage": "\\{\\{alertName\\}\\} est à l'état \\{\\{context.alertState\\}\\}\n\n\\{\\{context.metric\\}\\} était \\{\\{context.summary\\}\\} que la normale à \\{\\{context.timestamp\\}\\}\n\nValeur typique : \\{\\{context.typical\\}\\}\nValeur réelle : \\{\\{context.actual\\}\\}\n",
"xpack.infra.metrics.alerting.anomaly.fired": "Déclenché",
"xpack.infra.metrics.alerting.anomaly.memoryUsage": "Utilisation mémoire",
"xpack.infra.metrics.alerting.anomaly.networkIn": "Entrée réseau",
"xpack.infra.metrics.alerting.anomaly.networkOut": "Sortie réseau",
"xpack.infra.metrics.alerting.anomalyActualDescription": "Valeur réelle de l'indicateur monitoré au moment de l'anomalie.",
"xpack.infra.metrics.alerting.anomalyInfluencersDescription": "Liste des noms de nœuds ayant influencé l'anomalie.",
"xpack.infra.metrics.alerting.anomalyMetricDescription": "Nom de l'indicateur dans la condition spécifiée.",
"xpack.infra.metrics.alerting.anomalyScoreDescription": "Score de sévérité exact de l'anomalie détectée.",
"xpack.infra.metrics.alerting.anomalySummaryDescription": "Description de l'anomalie, par ex. \"2 x plus élevé.\"",
"xpack.infra.metrics.alerting.anomalyTimestampDescription": "Horodatage du moment où l'anomalie a été détectée.",
"xpack.infra.metrics.alerting.anomalyTypicalDescription": "Valeur typique de l'indicateur monitoré au moment de l'anomalie.",
"xpack.infra.metrics.alerting.cloudActionVariableDescription": "Objet cloud défini par ECS s'il est disponible dans la source.",
"xpack.infra.metrics.alerting.containerActionVariableDescription": "Objet conteneur défini par ECS s'il est disponible dans la source.",
"xpack.infra.metrics.alerting.groupActionVariableDescription": "Nom des données de reporting des groupes. Pour accéder à chaque clé de groupe, utilisez context.groupByKeys.",
@ -19154,8 +19126,6 @@
"xpack.infra.metrics.alerting.valueActionVariableDescription": "Valeur de l'indicateur dans la condition spécifiée. Utilisation : (ctx.value.condition0, ctx.value.condition1, etc...).",
"xpack.infra.metrics.alerting.viewInAppUrlActionVariableDescription": "Lien vers la source de lalerte",
"xpack.infra.metrics.alertName": "Seuil de l'indicateur",
"xpack.infra.metrics.anomaly.alertFlyout.alertDescription": "Alerte lorsque le score d'anomalie dépasse un seuil défini.",
"xpack.infra.metrics.anomaly.alertName": "Anomalie d'infrastructure",
"xpack.infra.metrics.emptyViewDescription": "Essayez d'ajuster vos heures ou votre filtre.",
"xpack.infra.metrics.emptyViewTitle": "Il n'y a aucune donnée à afficher.",
"xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "Fermer",

View file

@ -18483,8 +18483,6 @@
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "フィールド{name}",
"xpack.infra.metrics.alertFlyout.customEquationEditor.filterLabel": "KQLフィルター{name}",
"xpack.infra.metrics.alertFlyout.ofExpression.helpTextDetail": "メトリックが見つからない場合は、{documentationLink}。",
"xpack.infra.metrics.alerting.anomaly.summaryHigher": "{differential}x高い",
"xpack.infra.metrics.alerting.anomaly.summaryLower": "{differential}x低い",
"xpack.infra.metrics.alerting.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました",
"xpack.infra.metrics.alerting.threshold.firedAlertReason": "{metric}は最後の{duration}{group}の{currentValue}です。{comparator} {threshold}のときにアラートを通知します。",
"xpack.infra.metrics.alerting.threshold.noDataAlertReason": "{metric}は最後の{interval}{group}でデータがないことを報告しました",
@ -19065,12 +19063,6 @@
"xpack.infra.metrics.alertFlyout.alertOnGroupDisappear": "グループがデータのレポートを停止する場合にアラートで通知する",
"xpack.infra.metrics.alertFlyout.alertOnNoData": "データがない場合に通知する",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError.docsLink": "ドキュメント",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpText": "アラートトリガーの範囲を、特定のノードの影響を受ける異常に制限します。",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample": "例「my-node-1」または「my-node-*」",
"xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder": "すべて",
"xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage": "メモリー使用状況",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn": "内向きのネットワーク",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut": "外向きのネットワーク",
"xpack.infra.metrics.alertFlyout.conditions": "条件",
"xpack.infra.metrics.alertFlyout.createAlertPerHelpText": "すべての一意の値についてアラートを作成します。例「host.id」または「cloud.region」。",
"xpack.infra.metrics.alertFlyout.createAlertPerText": "アラートのグループ化条件(オプション)",
@ -19090,7 +19082,6 @@
"xpack.infra.metrics.alertFlyout.error.equation.invalidCharacters": "等式フィールドでは次の文字のみを使用できますA-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=",
"xpack.infra.metrics.alertFlyout.error.invalidFilterQuery": "フィルタークエリは無効です。",
"xpack.infra.metrics.alertFlyout.error.metricRequired": "メトリックが必要です。",
"xpack.infra.metrics.alertFlyout.error.mlCapabilitiesRequired": "機械学習が無効なときには、異常アラートを作成できません。",
"xpack.infra.metrics.alertFlyout.error.thresholdRequired": "しきい値が必要です。",
"xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired": "しきい値には有効な数値を含める必要があります。",
"xpack.infra.metrics.alertFlyout.error.timeRequred": "ページサイズが必要です。",
@ -19100,13 +19091,6 @@
"xpack.infra.metrics.alertFlyout.expression.metric.popoverTitle": "メトリック",
"xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel": "メトリックを選択",
"xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "タイミング",
"xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel": "重大",
"xpack.infra.metrics.alertFlyout.expression.severityScore.descriptionLabel": "重要度スコアが超えています",
"xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel": "高",
"xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel": "低",
"xpack.infra.metrics.alertFlyout.expression.severityScore.popoverTitle": "重要度スコア",
"xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel": "警告",
"xpack.infra.metrics.alertFlyout.filterByNodeLabel": "ノードでフィルタリング",
"xpack.infra.metrics.alertFlyout.filterHelpText": "KQL式を使用して、アラートトリガーの範囲を制限します。",
"xpack.infra.metrics.alertFlyout.filterLabel": "フィルター(任意)",
"xpack.infra.metrics.alertFlyout.groupDisappearHelpText": "以前に検出されたグループが結果を報告しなくなった場合は、これを有効にすると、アクションがトリガーされます。自動的に急速にノードを開始および停止することがある動的に拡張するインフラストラクチャーでは、これは推奨されません。",
@ -19118,18 +19102,6 @@
"xpack.infra.metrics.alertFlyout.warningThreshold": "警告",
"xpack.infra.metrics.alerting.alertDetailUrlActionVariableDescription": "アラートトラブルシューティングビューにリンクして、さらに詳しい状況や詳細を確認できます。server.publicBaseUrlが構成されていない場合は、空の文字列になります。",
"xpack.infra.metrics.alerting.alertStateActionVariableDescription": "現在のアラートの状態",
"xpack.infra.metrics.alerting.anomaly.defaultActionMessage": "\\{\\{alertName\\}\\}は\\{\\{context.alertState\\}\\}の状態です\n\n\\{\\{context.metric\\}\\}は\\{\\{context.timestamp\\}\\}で標準を超える\\{\\{context.summary\\}\\}でした\n\n標準の値\\{\\{context.typical\\}\\}\n実際の値\\{\\{context.actual\\}\\}\n",
"xpack.infra.metrics.alerting.anomaly.fired": "実行",
"xpack.infra.metrics.alerting.anomaly.memoryUsage": "メモリー使用状況",
"xpack.infra.metrics.alerting.anomaly.networkIn": "内向きのネットワーク",
"xpack.infra.metrics.alerting.anomaly.networkOut": "外向きのネットワーク",
"xpack.infra.metrics.alerting.anomalyActualDescription": "異常時に監視されたメトリックの実際の値。",
"xpack.infra.metrics.alerting.anomalyInfluencersDescription": "異常に影響したノード名のリスト。",
"xpack.infra.metrics.alerting.anomalyMetricDescription": "指定された条件のメトリック名。",
"xpack.infra.metrics.alerting.anomalyScoreDescription": "検出された異常の正確な重要度スコア。",
"xpack.infra.metrics.alerting.anomalySummaryDescription": "異常の説明。例「2x高い」",
"xpack.infra.metrics.alerting.anomalyTimestampDescription": "異常が検出された時点のタイムスタンプ。",
"xpack.infra.metrics.alerting.anomalyTypicalDescription": "異常時に監視されたメトリックの標準の値。",
"xpack.infra.metrics.alerting.cloudActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたクラウドオブジェクト。",
"xpack.infra.metrics.alerting.containerActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたコンテナーオブジェクト。",
"xpack.infra.metrics.alerting.groupActionVariableDescription": "データを報告するグループの名前。各グループキーにアクセスするには、context.groupByKeysを使用します。",
@ -19168,8 +19140,6 @@
"xpack.infra.metrics.alerting.valueActionVariableDescription": "指定された条件のメトリックの値。使用方法ctx.value.condition0、ctx.value.condition1など。",
"xpack.infra.metrics.alerting.viewInAppUrlActionVariableDescription": "アラートソースにリンク",
"xpack.infra.metrics.alertName": "メトリックしきい値",
"xpack.infra.metrics.anomaly.alertFlyout.alertDescription": "異常スコアが定義されたしきい値を超えたときにアラートを発行します。",
"xpack.infra.metrics.anomaly.alertName": "インフラストラクチャーの異常",
"xpack.infra.metrics.emptyViewDescription": "期間またはフィルターを調整してみてください。",
"xpack.infra.metrics.emptyViewTitle": "表示するデータがありません。",
"xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "閉じる",

View file

@ -18483,8 +18483,6 @@
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "字段 {name}",
"xpack.infra.metrics.alertFlyout.customEquationEditor.filterLabel": "KQL 筛选 {name}",
"xpack.infra.metrics.alertFlyout.ofExpression.helpTextDetail": "找不到指标?{documentationLink}。",
"xpack.infra.metrics.alerting.anomaly.summaryHigher": "高 {differential} 倍",
"xpack.infra.metrics.alerting.anomaly.summaryLower": "低 {differential} 倍",
"xpack.infra.metrics.alerting.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障",
"xpack.infra.metrics.alerting.threshold.firedAlertReason": "过去 {duration}{group}{metric} 为 {currentValue}。{comparator} {threshold} 时告警。",
"xpack.infra.metrics.alerting.threshold.noDataAlertReason": "对于 {group}{metric} 在过去 {interval}中未报告数据",
@ -19065,12 +19063,6 @@
"xpack.infra.metrics.alertFlyout.alertOnGroupDisappear": "组停止报告数据时提醒我",
"xpack.infra.metrics.alertFlyout.alertOnNoData": "没数据时提醒我",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError.docsLink": "文档",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpText": "将告警触发的范围限定在特定节点影响的异常。",
"xpack.infra.metrics.alertFlyout.anomalyFilterHelpTextExample": "例如“my-node-1”或“my-node-*”",
"xpack.infra.metrics.alertFlyout.anomalyInfluencerFilterPlaceholder": "所有内容",
"xpack.infra.metrics.alertFlyout.anomalyJobs.memoryUsage": "内存使用",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkIn": "网络传入",
"xpack.infra.metrics.alertFlyout.anomalyJobs.networkOut": "网络传出",
"xpack.infra.metrics.alertFlyout.conditions": "条件",
"xpack.infra.metrics.alertFlyout.createAlertPerHelpText": "为每个唯一值创建告警。例如“host.id”或“cloud.region”。",
"xpack.infra.metrics.alertFlyout.createAlertPerText": "告警分组依据(可选)",
@ -19090,7 +19082,6 @@
"xpack.infra.metrics.alertFlyout.error.equation.invalidCharacters": "方程字段仅支持以下字符A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=",
"xpack.infra.metrics.alertFlyout.error.invalidFilterQuery": "筛选查询无效。",
"xpack.infra.metrics.alertFlyout.error.metricRequired": "“指标”必填。",
"xpack.infra.metrics.alertFlyout.error.mlCapabilitiesRequired": "禁用 Machine Learning 时,无法创建异常告警。",
"xpack.infra.metrics.alertFlyout.error.thresholdRequired": "“阈值”必填。",
"xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired": "阈值必须包含有效数字。",
"xpack.infra.metrics.alertFlyout.error.timeRequred": "“时间大小”必填。",
@ -19100,13 +19091,6 @@
"xpack.infra.metrics.alertFlyout.expression.metric.popoverTitle": "指标",
"xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel": "选择指标",
"xpack.infra.metrics.alertFlyout.expression.metric.whenLabel": "当",
"xpack.infra.metrics.alertFlyout.expression.severityScore.criticalLabel": "紧急",
"xpack.infra.metrics.alertFlyout.expression.severityScore.descriptionLabel": "严重性分数高于",
"xpack.infra.metrics.alertFlyout.expression.severityScore.majorLabel": "重大",
"xpack.infra.metrics.alertFlyout.expression.severityScore.minorLabel": "轻微",
"xpack.infra.metrics.alertFlyout.expression.severityScore.popoverTitle": "严重性分数",
"xpack.infra.metrics.alertFlyout.expression.severityScore.warningLabel": "警告",
"xpack.infra.metrics.alertFlyout.filterByNodeLabel": "按节点筛选",
"xpack.infra.metrics.alertFlyout.filterHelpText": "使用 KQL 表达式限制告警触发的范围。",
"xpack.infra.metrics.alertFlyout.filterLabel": "筛选(可选)",
"xpack.infra.metrics.alertFlyout.groupDisappearHelpText": "启用此选项可在之前检测的组开始不报告任何数据时触发操作。不建议将此选项用于可能会快速自动启动和停止节点的动态扩展基础架构。",
@ -19118,18 +19102,6 @@
"xpack.infra.metrics.alertFlyout.warningThreshold": "警告",
"xpack.infra.metrics.alerting.alertDetailUrlActionVariableDescription": "链接到告警故障排除视图获取进一步的上下文和详情。如果未配置 server.publicBaseUrl这将为空字符串。",
"xpack.infra.metrics.alerting.alertStateActionVariableDescription": "告警的当前状态",
"xpack.infra.metrics.alerting.anomaly.defaultActionMessage": "\\{\\{alertName\\}\\} 处于 \\{\\{context.alertState\\}\\} 状态\n\n\\{\\{context.metric\\}\\} 在 \\{\\{context.timestamp\\}\\}比正常\\{\\{context.summary\\}\\}\n\n典型值\\{\\{context.typical\\}\\}\n实际值\\{\\{context.actual\\}\\}\n",
"xpack.infra.metrics.alerting.anomaly.fired": "已触发",
"xpack.infra.metrics.alerting.anomaly.memoryUsage": "内存使用",
"xpack.infra.metrics.alerting.anomaly.networkIn": "网络传入",
"xpack.infra.metrics.alerting.anomaly.networkOut": "网络传出",
"xpack.infra.metrics.alerting.anomalyActualDescription": "在发生异常时受监测指标的实际值。",
"xpack.infra.metrics.alerting.anomalyInfluencersDescription": "影响异常的节点名称列表。",
"xpack.infra.metrics.alerting.anomalyMetricDescription": "指定条件中的指标名称。",
"xpack.infra.metrics.alerting.anomalyScoreDescription": "检测到的异常的确切严重性分数。",
"xpack.infra.metrics.alerting.anomalySummaryDescription": "异常的描述,例如“高 2 倍”。",
"xpack.infra.metrics.alerting.anomalyTimestampDescription": "检测到异常时的时间戳。",
"xpack.infra.metrics.alerting.anomalyTypicalDescription": "在发生异常时受监测指标的典型值。",
"xpack.infra.metrics.alerting.cloudActionVariableDescription": "ECS 定义的云对象(如果在源中可用)。",
"xpack.infra.metrics.alerting.containerActionVariableDescription": "ECS 定义的容器对象(如果在源中可用)。",
"xpack.infra.metrics.alerting.groupActionVariableDescription": "报告数据的组名称。为了访问每个组密钥,请使用 context.groupByKeys。",
@ -19168,8 +19140,6 @@
"xpack.infra.metrics.alerting.valueActionVariableDescription": "指定条件中的指标值。用法:(ctx.value.condition0, ctx.value.condition1, 诸如此类)。",
"xpack.infra.metrics.alerting.viewInAppUrlActionVariableDescription": "链接到告警源",
"xpack.infra.metrics.alertName": "指标阈值",
"xpack.infra.metrics.anomaly.alertFlyout.alertDescription": "当异常分数超过定义的阈值时告警。",
"xpack.infra.metrics.anomaly.alertName": "基础架构异常",
"xpack.infra.metrics.emptyViewDescription": "尝试调整您的时间或筛选。",
"xpack.infra.metrics.emptyViewTitle": "没有可显示的数据。",
"xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "关闭",

View file

@ -46,7 +46,6 @@ export default function createRegisteredRuleTypeTests({ getService }: FtrProvide
'siem.newTermsRule',
'siem.notifications',
'slo.rules.burnRate',
'metrics.alert.anomaly',
'logs.alert.document.count',
'metrics.alert.inventory.threshold',
'metrics.alert.threshold',

View file

@ -81,7 +81,6 @@ export default function ({ getService }: FtrProviderContext) {
'alerting:apm.transaction_duration',
'alerting:apm.transaction_error_rate',
'alerting:logs.alert.document.count',
'alerting:metrics.alert.anomaly',
'alerting:metrics.alert.inventory.threshold',
'alerting:metrics.alert.threshold',
'alerting:monitoring_alert_cluster_health',

View file

@ -18,5 +18,9 @@ export default createTestConfig({
// include settings from project controller
// https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml
esServerArgs: ['xpack.ml.dfa.enabled=false', 'xpack.ml.nlp.enabled=false'],
kbnServerArgs: ['--xpack.infra.enabled=true'],
kbnServerArgs: [
'--xpack.infra.enabled=true',
'--xpack.infra.featureFlags.customThresholdAlertsEnabled=true',
'--xpack.observability.unsafe.thresholdRule.enabled=true',
],
});