mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] Add anomaly description as an alert message for anomaly detection rule type (#172473)
## Summary
Closes #136391
Uses a description of the anomaly for the alert message for anomaly
detection alerting rules with the `record` result type. This messages is
used for example in the `Reason` field in the alert table and details
flyout.
<img width="753" alt="image"
src="072fe833
-204b-4d38-bd3d-50d00015a43f">
### Checklist
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
parent
3ff891003c
commit
50dabea70f
10 changed files with 161 additions and 85 deletions
69
x-pack/plugins/ml/common/util/anomaly_description.ts
Normal file
69
x-pack/plugins/ml/common/util/anomaly_description.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { capitalize } from 'lodash';
|
||||
import { getSeverity, type MlAnomaliesTableRecordExtended } from '@kbn/ml-anomaly-utils';
|
||||
|
||||
export function getAnomalyDescription(anomaly: MlAnomaliesTableRecordExtended): {
|
||||
anomalyDescription: string;
|
||||
mvDescription: string | undefined;
|
||||
} {
|
||||
const source = anomaly.source;
|
||||
|
||||
let anomalyDescription = i18n.translate('xpack.ml.anomalyDescription.anomalyInLabel', {
|
||||
defaultMessage: '{anomalySeverity} anomaly in {anomalyDetector}',
|
||||
values: {
|
||||
anomalySeverity: capitalize(getSeverity(anomaly.severity).label),
|
||||
anomalyDetector: anomaly.detector,
|
||||
},
|
||||
});
|
||||
|
||||
if (anomaly.entityName !== undefined) {
|
||||
anomalyDescription += i18n.translate('xpack.ml.anomalyDescription.foundForLabel', {
|
||||
defaultMessage: ' found for {anomalyEntityName} {anomalyEntityValue}',
|
||||
values: {
|
||||
anomalyEntityName: anomaly.entityName,
|
||||
anomalyEntityValue: anomaly.entityValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
source.partition_field_name !== undefined &&
|
||||
source.partition_field_name !== anomaly.entityName
|
||||
) {
|
||||
anomalyDescription += i18n.translate('xpack.ml.anomalyDescription.detectedInLabel', {
|
||||
defaultMessage: ' detected in {sourcePartitionFieldName} {sourcePartitionFieldValue}',
|
||||
values: {
|
||||
sourcePartitionFieldName: source.partition_field_name,
|
||||
sourcePartitionFieldValue: source.partition_field_value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check for a correlatedByFieldValue in the source which will be present for multivariate analyses
|
||||
// where the record is anomalous due to relationship with another 'by' field value.
|
||||
let mvDescription: string = '';
|
||||
if (source.correlated_by_field_value !== undefined) {
|
||||
mvDescription = i18n.translate('xpack.ml.anomalyDescription.multivariateDescription', {
|
||||
defaultMessage:
|
||||
'multivariate correlations found in {sourceByFieldName}; ' +
|
||||
'{sourceByFieldValue} is considered anomalous given {sourceCorrelatedByFieldValue}',
|
||||
values: {
|
||||
sourceByFieldName: source.by_field_name,
|
||||
sourceByFieldValue: source.by_field_value,
|
||||
sourceCorrelatedByFieldValue: source.correlated_by_field_value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
anomalyDescription,
|
||||
mvDescription,
|
||||
};
|
||||
}
|
|
@ -15,8 +15,8 @@ import { i18n } from '@kbn/i18n';
|
|||
// Returns an Object containing a text message and EuiIcon type to
|
||||
// describe how the actual value compares to the typical.
|
||||
export function getMetricChangeDescription(
|
||||
actualProp: number[] | number,
|
||||
typicalProp: number[] | number
|
||||
actualProp: number[] | number | undefined,
|
||||
typicalProp: number[] | number | undefined
|
||||
) {
|
||||
if (actualProp === undefined || typicalProp === undefined) {
|
||||
return { iconType: 'empty', message: '' };
|
|
@ -10,11 +10,9 @@
|
|||
* of the anomalies table.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useMemo } from 'react';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -27,17 +25,16 @@ import {
|
|||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { getSeverity, type MlAnomaliesTableRecordExtended } from '@kbn/ml-anomaly-utils';
|
||||
|
||||
import { type MlAnomaliesTableRecordExtended } from '@kbn/ml-anomaly-utils';
|
||||
import { getAnomalyDescription } from '../../../../common/util/anomaly_description';
|
||||
import { MAX_CHARS } from './anomalies_table_constants';
|
||||
import type { CategoryDefinition } from '../../services/ml_api_service/results';
|
||||
import { EntityCellFilter } from '../entity_cell';
|
||||
import { ExplorerJob } from '../../explorer/explorer_utils';
|
||||
|
||||
import {
|
||||
getInfluencersItems,
|
||||
AnomalyExplanationDetails,
|
||||
DetailsItems,
|
||||
getInfluencersItems,
|
||||
} from './anomaly_details_utils';
|
||||
|
||||
interface Props {
|
||||
|
@ -166,56 +163,7 @@ const Contents: FC<{
|
|||
};
|
||||
|
||||
const Description: FC<{ anomaly: MlAnomaliesTableRecordExtended }> = ({ anomaly }) => {
|
||||
const source = anomaly.source;
|
||||
|
||||
let anomalyDescription = i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel', {
|
||||
defaultMessage: '{anomalySeverity} anomaly in {anomalyDetector}',
|
||||
values: {
|
||||
anomalySeverity: capitalize(getSeverity(anomaly.severity).label),
|
||||
anomalyDetector: anomaly.detector,
|
||||
},
|
||||
});
|
||||
if (anomaly.entityName !== undefined) {
|
||||
anomalyDescription += i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.foundForLabel', {
|
||||
defaultMessage: ' found for {anomalyEntityName} {anomalyEntityValue}',
|
||||
values: {
|
||||
anomalyEntityName: anomaly.entityName,
|
||||
anomalyEntityValue: anomaly.entityValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
source.partition_field_name !== undefined &&
|
||||
source.partition_field_name !== anomaly.entityName
|
||||
) {
|
||||
anomalyDescription += i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel', {
|
||||
defaultMessage: ' detected in {sourcePartitionFieldName} {sourcePartitionFieldValue}',
|
||||
values: {
|
||||
sourcePartitionFieldName: source.partition_field_name,
|
||||
sourcePartitionFieldValue: source.partition_field_value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check for a correlatedByFieldValue in the source which will be present for multivariate analyses
|
||||
// where the record is anomalous due to relationship with another 'by' field value.
|
||||
let mvDescription;
|
||||
if (source.correlated_by_field_value !== undefined) {
|
||||
mvDescription = i18n.translate(
|
||||
'xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'multivariate correlations found in {sourceByFieldName}; ' +
|
||||
'{sourceByFieldValue} is considered anomalous given {sourceCorrelatedByFieldValue}',
|
||||
values: {
|
||||
sourceByFieldName: source.by_field_name,
|
||||
sourceByFieldValue: source.by_field_value,
|
||||
sourceCorrelatedByFieldValue: source.correlated_by_field_value,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
const { anomalyDescription, mvDescription } = getAnomalyDescription(anomaly);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
|
||||
|
||||
import { getMetricChangeDescription } from '../../formatters/metric_change_description';
|
||||
import { getMetricChangeDescription } from '../../../../common/util/metric_change_description';
|
||||
|
||||
/*
|
||||
* Component for rendering the description cell in the anomalies table, which provides a
|
||||
|
|
|
@ -13,7 +13,7 @@ export * from '../common/types/audit_message';
|
|||
|
||||
export * from '../common/util/validators';
|
||||
|
||||
export * from './application/formatters/metric_change_description';
|
||||
export * from '../common/util/metric_change_description';
|
||||
export * from './application/components/field_stats_flyout';
|
||||
export * from './application/data_frame_analytics/common';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import Boom from '@hapi/boom';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import rison from '@kbn/rison';
|
||||
import type { Duration } from 'moment/moment';
|
||||
import { memoize, pick } from 'lodash';
|
||||
import { capitalize, get, memoize, pick } from 'lodash';
|
||||
import {
|
||||
FIELD_FORMAT_IDS,
|
||||
type IFieldFormat,
|
||||
|
@ -22,9 +22,13 @@ import {
|
|||
type MlAnomalyRecordDoc,
|
||||
type MlAnomalyResultType,
|
||||
ML_ANOMALY_RESULT_TYPE,
|
||||
MlAnomaliesTableRecordExtended,
|
||||
} from '@kbn/ml-anomaly-utils';
|
||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||
import { ALERT_REASON, ALERT_URL } from '@kbn/rule-data-utils';
|
||||
import { MlJob } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { getAnomalyDescription } from '../../../common/util/anomaly_description';
|
||||
import { getMetricChangeDescription } from '../../../common/util/metric_change_description';
|
||||
import type { MlClient } from '../ml_client';
|
||||
import type {
|
||||
MlAnomalyDetectionAlertParams,
|
||||
|
@ -184,6 +188,8 @@ export function alertingServiceProvider(
|
|||
) {
|
||||
type FieldFormatters = AwaitReturnType<ReturnType<typeof getFormatters>>;
|
||||
|
||||
let jobs: MlJob[] = [];
|
||||
|
||||
/**
|
||||
* Provides formatters based on the data view of the datafeed index pattern
|
||||
* and set of default formatters for fallback.
|
||||
|
@ -397,6 +403,72 @@ export function alertingServiceProvider(
|
|||
return alertInstanceKey;
|
||||
};
|
||||
|
||||
const getAlertMessage = (
|
||||
resultType: MlAnomalyResultType,
|
||||
source: Record<string, unknown>
|
||||
): string => {
|
||||
let message = i18n.translate('xpack.ml.alertTypes.anomalyDetectionAlertingRule.alertMessage', {
|
||||
defaultMessage:
|
||||
'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.',
|
||||
});
|
||||
|
||||
if (resultType === ML_ANOMALY_RESULT_TYPE.RECORD) {
|
||||
const recordSource = source as MlAnomalyRecordDoc;
|
||||
|
||||
const detectorsByJob = jobs.reduce((acc, job) => {
|
||||
acc[job.job_id] = job.analysis_config.detectors.reduce((innterAcc, detector) => {
|
||||
innterAcc[detector.detector_index!] = detector.detector_description;
|
||||
return innterAcc;
|
||||
}, {} as Record<number, string | undefined>);
|
||||
return acc;
|
||||
}, {} as Record<string, Record<number, string | undefined>>);
|
||||
|
||||
const detectorDescription = get(detectorsByJob, [
|
||||
recordSource.job_id,
|
||||
recordSource.detector_index,
|
||||
]);
|
||||
|
||||
const record = {
|
||||
source: recordSource,
|
||||
detector: detectorDescription ?? recordSource.function_description,
|
||||
severity: recordSource.record_score,
|
||||
} as MlAnomaliesTableRecordExtended;
|
||||
const entityName = getEntityFieldName(recordSource);
|
||||
if (entityName !== undefined) {
|
||||
record.entityName = entityName;
|
||||
record.entityValue = getEntityFieldValue(recordSource);
|
||||
}
|
||||
|
||||
const { anomalyDescription, mvDescription } = getAnomalyDescription(record);
|
||||
|
||||
const anomalyDescriptionSummary = `${anomalyDescription}${
|
||||
mvDescription ? ` (${mvDescription})` : ''
|
||||
}`;
|
||||
|
||||
let actual = recordSource.actual;
|
||||
let typical = recordSource.typical;
|
||||
if (
|
||||
(!isDefined(actual) || !isDefined(typical)) &&
|
||||
Array.isArray(recordSource.causes) &&
|
||||
recordSource.causes.length === 1
|
||||
) {
|
||||
actual = recordSource.causes[0].actual;
|
||||
typical = recordSource.causes[0].typical;
|
||||
}
|
||||
|
||||
let metricChangeDescription = '';
|
||||
if (isDefined(actual) && isDefined(typical)) {
|
||||
metricChangeDescription = capitalize(getMetricChangeDescription(actual, typical).message);
|
||||
}
|
||||
|
||||
message = `${anomalyDescriptionSummary}. ${
|
||||
metricChangeDescription ? `${metricChangeDescription}.` : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a callback for formatting elasticsearch aggregation response
|
||||
* to the alert-as-data document.
|
||||
|
@ -419,14 +491,10 @@ export function alertingServiceProvider(
|
|||
const topAnomaly = requestedAnomalies[0];
|
||||
const timestamp = topAnomaly._source.timestamp;
|
||||
|
||||
const message = getAlertMessage(resultType, topAnomaly._source);
|
||||
|
||||
return {
|
||||
[ALERT_REASON]: i18n.translate(
|
||||
'xpack.ml.alertTypes.anomalyDetectionAlertingRule.alertMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.',
|
||||
}
|
||||
),
|
||||
[ALERT_REASON]: message,
|
||||
job_id: [...new Set(requestedAnomalies.map((h) => h._source.job_id))][0],
|
||||
is_interim: requestedAnomalies.some((h) => h._source.is_interim),
|
||||
anomaly_timestamp: timestamp,
|
||||
|
@ -495,14 +563,12 @@ export function alertingServiceProvider(
|
|||
const alertInstanceKey = getAlertInstanceKey(topAnomaly._source);
|
||||
const timestamp = topAnomaly._source.timestamp;
|
||||
const bucketSpanInSeconds = topAnomaly._source.bucket_span;
|
||||
const message = getAlertMessage(resultType, topAnomaly._source);
|
||||
|
||||
return {
|
||||
count: aggTypeResults.doc_count,
|
||||
key: v.key,
|
||||
message: i18n.translate('xpack.ml.alertTypes.anomalyDetectionAlertingRule.alertMessage', {
|
||||
defaultMessage:
|
||||
'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.',
|
||||
}),
|
||||
message,
|
||||
alertInstanceKey,
|
||||
jobIds: [...new Set(requestedAnomalies.map((h) => h._source.job_id))],
|
||||
isInterim: requestedAnomalies.some((h) => h._source.is_interim),
|
||||
|
@ -564,6 +630,8 @@ export function alertingServiceProvider(
|
|||
// Extract jobs from group ids and make sure provided jobs assigned to a current space
|
||||
const jobsResponse = (await mlClient.getJobs({ job_id: jobAndGroupIds.join(',') })).jobs;
|
||||
|
||||
jobs = jobsResponse;
|
||||
|
||||
if (jobsResponse.length === 0) {
|
||||
// Probably assigned groups don't contain any jobs anymore.
|
||||
throw new Error("Couldn't find the job with provided id");
|
||||
|
@ -699,6 +767,9 @@ export function alertingServiceProvider(
|
|||
// Extract jobs from group ids and make sure provided jobs assigned to a current space
|
||||
const jobsResponse = (await mlClient.getJobs({ job_id: jobAndGroupIds.join(',') })).jobs;
|
||||
|
||||
// Cache jobs response
|
||||
jobs = jobsResponse;
|
||||
|
||||
if (jobsResponse.length === 0) {
|
||||
// Probably assigned groups don't contain any jobs anymore.
|
||||
return;
|
||||
|
|
|
@ -24189,13 +24189,9 @@
|
|||
"xpack.ml.annotationsTable.howToCreateAnnotationDescription": "Pour créer une annotation, ouvrir le {linkToSingleMetricView}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText": "et {othersCount} en plus",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationTitle": "Explication des anomalies {learnMoreLink}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel": "{anomalySeverity} anomalie dans {anomalyDetector}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyTimeRangeLabel": "{anomalyTime} à {anomalyEndTime}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesDescription": "{causeEntityValue} (actuel {actualValue}, typique {typicalValue}, probabilité {probabilityValue})",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesTitle": "Valeurs {causeEntityName}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel": " détecté dans {sourcePartitionFieldName} {sourcePartitionFieldValue}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.foundForLabel": " trouvé pour {anomalyEntityName} {anomalyEntityValue}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription": "corrélations multi-variable trouvées dans {sourceByFieldName} ; {sourceByFieldValue} est considérée comme une anomalie étant donné {sourceCorrelatedByFieldValue}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionTooltip": "L'expression normale qui est utilisée pour rechercher des valeurs correspondant à la catégorie (peut être tronquée à une limite de caractères max de {maxChars})",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionTooltip": "Une liste des jetons communs séparés par un espace correspondant aux valeurs de la catégorie (peut être tronquée à une limite de caractères max. de {maxChars})",
|
||||
"xpack.ml.anomaliesTable.anomalyExplanationDetails.anomalyType.dip": "Baisse sur {anomalyLength, plural, one {# compartiment} many {# compartiments} other {# compartiments}}",
|
||||
|
|
|
@ -24204,13 +24204,9 @@
|
|||
"xpack.ml.annotationsTable.howToCreateAnnotationDescription": "注釈を作成するには、{linkToSingleMetricView} を開きます",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText": "他{othersCount}件",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationTitle": "異常の説明{learnMoreLink}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel": "{anomalyDetector} の {anomalySeverity} の異常",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyTimeRangeLabel": "{anomalyTime}から{anomalyEndTime}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesDescription": "{causeEntityValue} (実際値 {actualValue}、通常値 {typicalValue}、確率 {probabilityValue})",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesTitle": "{causeEntityName}値",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel": " {sourcePartitionFieldName} {sourcePartitionFieldValue} で検知",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.foundForLabel": " {anomalyEntityName} {anomalyEntityValue}に対して見つかりました",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription": "{sourceByFieldName} で多変量相関が見つかりました; {sourceByFieldValue} は {sourceCorrelatedByFieldValue} のため異例とみなされます",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionTooltip": "カテゴリーが一致する値を検索するのに使用される正規表現です({maxChars}文字の制限で切り捨てられている可能性があります)",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionTooltip": "カテゴリーの値で一致している共通のトークンのスペース区切りのリストです({maxChars}文字の制限で切り捨てられている可能性があります)",
|
||||
"xpack.ml.anomaliesTable.anomalyExplanationDetails.anomalyType.dip": "{anomalyLength, plural, other {#個のバケット}}でディップ",
|
||||
|
|
|
@ -24203,13 +24203,9 @@
|
|||
"xpack.ml.annotationsTable.howToCreateAnnotationDescription": "要创建注释,请打开 {linkToSingleMetricView}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText": "及另外 {othersCount} 个",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationTitle": "异常解释 {learnMoreLink}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel": "{anomalyDetector} 中的 {anomalySeverity} 异常",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.anomalyTimeRangeLabel": "{anomalyTime} 至 {anomalyEndTime}",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesDescription": "{causeEntityValue}(实际 {actualValue}典型 {typicalValue}可能性 {probabilityValue})",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.causeValuesTitle": "{causeEntityName} 值",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel": " 在 {sourcePartitionFieldName} {sourcePartitionFieldValue} 检测到",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.foundForLabel": " 已为 {anomalyEntityName} {anomalyEntityValue} 找到",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription": "{sourceByFieldName} 中找到多变量关联;如果{sourceCorrelatedByFieldValue},{sourceByFieldValue} 将被视为有异常",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionTooltip": "用于搜索匹配该类别的值(可能已截短至最大字符限制 {maxChars})的正则表达式",
|
||||
"xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionTooltip": "该类别的值(可能已截短至最大字符限制({maxChars})中匹配的常见令牌的空格分隔列表",
|
||||
"xpack.ml.anomaliesTable.anomalyExplanationDetails.anomalyType.dip": "{anomalyLength, plural, other {# 个存储桶}}上出现谷值",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue