mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Fix creation of alert instances for Anomaly detection alert type (#93605)
* [ML] use static alert instance key, remove logic for handling duplicates * [ML] use job id as an alert instance id * [ML] beta label * [ML] round time interval * [ML] fix preview button * [ML] improve the default template * [ML] remove redundant kibanaBaseUrl
This commit is contained in:
parent
5755d7907c
commit
15cda17f2d
7 changed files with 41 additions and 98 deletions
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiSpacer, EuiForm } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiForm, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { JobSelectorControl } from './job_selector';
|
||||
|
@ -116,6 +116,22 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({
|
|||
|
||||
return (
|
||||
<EuiForm data-test-subj={'mlAnomalyAlertForm'}>
|
||||
<EuiFlexGroup gutterSize={'none'} justifyContent={'flexEnd'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate('xpack.ml.anomalyDetectionAlert.betaBadgeLabel', {
|
||||
defaultMessage: 'Beta',
|
||||
})}
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.ml.anomalyDetectionAlert.betaBadgeTooltipContent',
|
||||
{
|
||||
defaultMessage: `Anomaly detection alerts are a beta feature. We'd love to hear your feedback.`,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<JobSelectorControl
|
||||
jobsAndGroupIds={jobsAndGroupIds}
|
||||
adJobsApiService={adJobsApiService}
|
||||
|
|
|
@ -152,7 +152,7 @@ export const PreviewAlertCondition: FC<PreviewAlertConditionProps> = ({
|
|||
(alertParams.jobSelection?.jobIds?.length! > 0 ||
|
||||
alertParams.jobSelection?.groupIds?.length! > 0) &&
|
||||
!!alertParams.resultType &&
|
||||
!!alertParams.severity &&
|
||||
alertParams.severity !== undefined &&
|
||||
validationErrors === null;
|
||||
|
||||
const isInvalid = lookBehindInterval !== undefined && !!validationErrors;
|
||||
|
|
|
@ -65,25 +65,28 @@ export function registerMlAlerts(triggersActionsUi: TriggersAndActionsUIPublicPl
|
|||
'xpack.ml.alertTypes.anomalyDetection.defaultActionMessage',
|
||||
{
|
||||
defaultMessage: `Elastic Stack Machine Learning Alert:
|
||||
- Job IDs: \\{\\{#context.jobIds\\}\\}\\{\\{context.jobIds\\}\\} - \\{\\{/context.jobIds\\}\\}
|
||||
- Job IDs: \\{\\{context.jobIds\\}\\}
|
||||
- Time: \\{\\{context.timestampIso8601\\}\\}
|
||||
- Anomaly score: \\{\\{context.score\\}\\}
|
||||
|
||||
Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.
|
||||
|
||||
\\{\\{! Section might be not relevant if selected jobs don't contain influencer configuration \\}\\}
|
||||
Top influencers:
|
||||
\\{\\{#context.topInfluencers\\}\\}
|
||||
\\{\\{influencer_field_name\\}\\} = \\{\\{influencer_field_value\\}\\} [\\{\\{score\\}\\}]
|
||||
\\{\\{/context.topInfluencers\\}\\}
|
||||
\\{\\{#context.topInfluencers.length\\}\\}
|
||||
Top influencers:
|
||||
\\{\\{#context.topInfluencers\\}\\}
|
||||
\\{\\{influencer_field_name\\}\\} = \\{\\{influencer_field_value\\}\\} [\\{\\{score\\}\\}]
|
||||
\\{\\{/context.topInfluencers\\}\\}
|
||||
\\{\\{/context.topInfluencers.length\\}\\}
|
||||
|
||||
Top records:
|
||||
\\{\\{#context.topRecords\\}\\}
|
||||
\\{\\{function\\}\\}(\\{\\{field_name\\}\\}) \\{\\{by_field_value\\}\\} \\{\\{over_field_value\\}\\} \\{\\{partition_field_value\\}\\} [\\{\\{score\\}\\}]
|
||||
\\{\\{/context.topRecords\\}\\}
|
||||
\\{\\{#context.topRecords.length\\}\\}
|
||||
Top records:
|
||||
\\{\\{#context.topRecords\\}\\}
|
||||
\\{\\{function\\}\\}(\\{\\{field_name\\}\\}) \\{\\{by_field_value\\}\\} \\{\\{over_field_value\\}\\} \\{\\{partition_field_value\\}\\} [\\{\\{score\\}\\}]
|
||||
\\{\\{/context.topRecords\\}\\}
|
||||
\\{\\{/context.topRecords.length\\}\\}
|
||||
|
||||
\\{\\{! Replace kibanaBaseUrl if not configured in Kibana \\}\\}
|
||||
[Open in Anomaly Explorer](\\{\\{\\{context.kibanaBaseUrl\\}\\}\\}\\{\\{\\{context.anomalyExplorerUrl\\}\\}\\})
|
||||
[Open in Anomaly Explorer](\\{\\{\\{kibanaBaseUrl\\}\\}\\}\\{\\{\\{context.anomalyExplorerUrl\\}\\}\\})
|
||||
`,
|
||||
}
|
||||
),
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
MlAnomalyDetectionAlertPreviewRequest,
|
||||
} from '../../routes/schemas/alerting_schema';
|
||||
import { ANOMALY_RESULT_TYPE } from '../../../common/constants/anomalies';
|
||||
import { AnomalyResultType } from '../../../common/types/anomalies';
|
||||
import { AnomalyRecordDoc, AnomalyResultType } from '../../../common/types/anomalies';
|
||||
import {
|
||||
AlertExecutionResult,
|
||||
InfluencerAnomalyAlertDoc,
|
||||
|
@ -27,8 +27,6 @@ import {
|
|||
} from '../../../common/types/alerts';
|
||||
import { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type';
|
||||
import { MlJobsResponse } from '../../../common/types/job_service';
|
||||
import { ANOMALY_SCORE_MATCH_GROUP_ID } from '../../../common/constants/alerts';
|
||||
import { getEntityFieldName, getEntityFieldValue } from '../../../common/util/anomaly_utils';
|
||||
import { resolveBucketSpanInSeconds } from '../../../common/util/job_utils';
|
||||
|
||||
/**
|
||||
|
@ -249,20 +247,10 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea
|
|||
};
|
||||
|
||||
/**
|
||||
* Provides unique key for the anomaly result.
|
||||
* Provides a key for alert instance.
|
||||
*/
|
||||
const getAlertInstanceKey = (source: any): string => {
|
||||
let alertInstanceKey = `${source.job_id}_${source.timestamp}`;
|
||||
if (source.result_type === ANOMALY_RESULT_TYPE.INFLUENCER) {
|
||||
alertInstanceKey += `_${source.influencer_field_name}_${source.influencer_field_value}`;
|
||||
} else if (source.result_type === ANOMALY_RESULT_TYPE.RECORD) {
|
||||
const fieldName = getEntityFieldName(source);
|
||||
const fieldValue = getEntityFieldValue(source);
|
||||
const entity =
|
||||
fieldName !== undefined && fieldValue !== undefined ? `_${fieldName}_${fieldValue}` : '';
|
||||
alertInstanceKey += `_${source.detector_index}_${source.function}${entity}`;
|
||||
}
|
||||
return alertInstanceKey;
|
||||
const getAlertInstanceKey = (source: AnomalyRecordDoc): string => {
|
||||
return source.job_id;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -297,8 +285,10 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea
|
|||
*/
|
||||
const lookBackTimeInterval = `${Math.max(
|
||||
// Double the max bucket span
|
||||
resolveBucketSpanInSeconds(jobsResponse.map((v) => v.analysis_config.bucket_span)) * 2,
|
||||
checkIntervalGap ? checkIntervalGap.asSeconds() : 0
|
||||
Math.round(
|
||||
resolveBucketSpanInSeconds(jobsResponse.map((v) => v.analysis_config.bucket_span)) * 2
|
||||
),
|
||||
checkIntervalGap ? Math.round(checkIntervalGap.asSeconds()) : 0
|
||||
)}s`;
|
||||
|
||||
const jobIds = jobsResponse.map((v) => v.job_id);
|
||||
|
@ -499,15 +489,11 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea
|
|||
* Return the result of an alert condition execution.
|
||||
*
|
||||
* @param params - Alert params
|
||||
* @param publicBaseUrl
|
||||
* @param alertId - Alert ID
|
||||
* @param startedAt
|
||||
* @param previousStartedAt
|
||||
*/
|
||||
execute: async (
|
||||
params: MlAnomalyDetectionAlertParams,
|
||||
publicBaseUrl: string | undefined,
|
||||
alertId: string,
|
||||
startedAt: Date,
|
||||
previousStartedAt: Date | null
|
||||
): Promise<AnomalyDetectionAlertContext | undefined> => {
|
||||
|
@ -530,60 +516,8 @@ export function alertingServiceProvider(mlClient: MlClient, esClient: Elasticsea
|
|||
...result,
|
||||
name: result.alertInstanceKey,
|
||||
anomalyExplorerUrl,
|
||||
kibanaBaseUrl: publicBaseUrl!,
|
||||
};
|
||||
|
||||
let kibanaEventLogCount = 0;
|
||||
try {
|
||||
// Check kibana-event-logs for presence of this alert instance
|
||||
const kibanaLogResults = await esClient.count({
|
||||
index: '.kibana-event-log-*',
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alerting.action_group_id': {
|
||||
value: ANOMALY_SCORE_MATCH_GROUP_ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.alerting.instance_id': {
|
||||
value: executionResult.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
nested: {
|
||||
path: 'kibana.saved_objects',
|
||||
query: {
|
||||
term: {
|
||||
'kibana.saved_objects.id': {
|
||||
value: alertId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
kibanaEventLogCount = kibanaLogResults.body.count;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Unable to check kibana event logs', e);
|
||||
}
|
||||
|
||||
if (kibanaEventLogCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return executionResult;
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -38,13 +38,11 @@ export type AnomalyDetectionAlertContext = {
|
|||
topRecords: RecordAnomalyAlertDoc[];
|
||||
topInfluencers?: InfluencerAnomalyAlertDoc[];
|
||||
anomalyExplorerUrl: string;
|
||||
kibanaBaseUrl: string;
|
||||
} & AlertInstanceContext;
|
||||
|
||||
export function registerAnomalyDetectionAlertType({
|
||||
alerts,
|
||||
mlSharedServices,
|
||||
publicBaseUrl,
|
||||
}: RegisterAlertParams) {
|
||||
alerts.registerType<
|
||||
MlAnomalyDetectionAlertParams,
|
||||
|
@ -129,13 +127,7 @@ export function registerAnomalyDetectionAlertType({
|
|||
services.savedObjectsClient,
|
||||
fakeRequest
|
||||
);
|
||||
const executionResult = await execute(
|
||||
params,
|
||||
publicBaseUrl,
|
||||
alertId,
|
||||
startedAt,
|
||||
previousStartedAt
|
||||
);
|
||||
const executionResult = await execute(params, startedAt, previousStartedAt);
|
||||
|
||||
if (executionResult) {
|
||||
const alertInstanceName = executionResult.name;
|
||||
|
|
|
@ -14,7 +14,6 @@ export interface RegisterAlertParams {
|
|||
alerts: AlertingPlugin['setup'];
|
||||
logger: Logger;
|
||||
mlSharedServices: SharedServices;
|
||||
publicBaseUrl: string | undefined;
|
||||
}
|
||||
|
||||
export function registerMlAlerts(params: RegisterAlertParams) {
|
||||
|
|
|
@ -213,7 +213,6 @@ export class MlServerPlugin
|
|||
alerts: plugins.alerts,
|
||||
logger: this.log,
|
||||
mlSharedServices: sharedServices,
|
||||
publicBaseUrl: coreSetup.http.basePath.publicBaseUrl,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue