Skip to content

Commit

Permalink
[ML] Transforms: use health information for alerting rule (elastic#15…
Browse files Browse the repository at this point in the history
  • Loading branch information
darnautov authored and bmorelli25 committed Mar 10, 2023
1 parent 0f1c4e0 commit d339b75
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 70 deletions.
11 changes: 11 additions & 0 deletions x-pack/plugins/transform/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ export const TRANSFORM_HEALTH_CHECK_NAMES: Record<
}
),
},
healthCheck: {
name: i18n.translate('xpack.transform.alertTypes.transformHealth.healthCheckName', {
defaultMessage: 'Unhealthy transform',
}),
description: i18n.translate(
'xpack.transform.alertTypes.transformHealth.healthCheckDescription',
{
defaultMessage: 'Get alerts if a transform health status is not green.',
}
),
},
};

// Transform API default values https://www.elastic.co/guide/en/elasticsearch/reference/current/put-transform.html
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/transform/common/types/alerting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ export type TransformHealthRuleParams = {
notStarted?: {
enabled: boolean;
} | null;
/**
* @deprecated replaced in favor of healthCheck in 8.8
*/
errorMessages?: {
enabled: boolean;
} | null;
healthCheck?: {
enabled: boolean;
} | null;
} | null;
} & RuleTypeParams;

Expand Down
108 changes: 108 additions & 0 deletions x-pack/plugins/transform/common/utils/alerts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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 { getResultTestConfig } from './alerts';

describe('getResultTestConfig', () => {
test('provides default config for new rule', () => {
expect(getResultTestConfig(undefined)).toEqual({
healthCheck: {
enabled: true,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: false,
},
});
});

test('provides config for rule created with default settings', () => {
expect(getResultTestConfig(null)).toEqual({
healthCheck: {
enabled: true,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: false,
},
});
});

test('completes already defined config', () => {
expect(
getResultTestConfig({
healthCheck: null,
notStarted: null,
errorMessages: {
enabled: false,
},
})
).toEqual({
healthCheck: {
enabled: false,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: false,
},
});
});

test('sets healthCheck based on the errorMessages', () => {
expect(
getResultTestConfig({
healthCheck: null,
notStarted: null,
errorMessages: {
enabled: true,
},
})
).toEqual({
healthCheck: {
enabled: false,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: true,
},
});
});

test('preserves complete config', () => {
expect(
getResultTestConfig({
healthCheck: {
enabled: false,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: true,
},
})
).toEqual({
healthCheck: {
enabled: false,
},
notStarted: {
enabled: true,
},
errorMessages: {
enabled: true,
},
});
});
});
15 changes: 14 additions & 1 deletion x-pack/plugins/transform/common/utils/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@
import type { TransformHealthRuleTestsConfig } from '../types/alerting';

export function getResultTestConfig(config: TransformHealthRuleTestsConfig) {
let healthCheckEnabled = true;

if (typeof config?.healthCheck?.enabled === 'boolean') {
healthCheckEnabled = config?.healthCheck?.enabled;
} else if (typeof config?.errorMessages?.enabled === 'boolean') {
// if errorMessages test has been explicitly enabled / disabled,
// also disabled the healthCheck test
healthCheckEnabled = false;
}

return {
notStarted: {
enabled: config?.notStarted?.enabled ?? true,
},
errorMessages: {
enabled: config?.errorMessages?.enabled ?? true,
enabled: config?.errorMessages?.enabled ?? false,
},
healthCheck: {
enabled: healthCheckEnabled,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,17 @@ export function getTransformHealthRuleType(): RuleTypeModel<TransformHealthRuleP
Transform ID: \\{\\{transform_id\\}\\}
\\{\\{#description\\}\\}Transform description: \\{\\{description\\}\\}
\\{\\{/description\\}\\}\\{\\{#transform_state\\}\\}Transform state: \\{\\{transform_state\\}\\}
\\{\\{/transform_state\\}\\}\\{\\{#failure_reason\\}\\}Failure reason: \\{\\{failure_reason\\}\\}
\\{\\{/transform_state\\}\\}\\{\\{#health_status\\}\\}Transform health status: \\{\\{health_status\\}\\}
\\{\\{/health_status\\}\\}\\{\\{#issues\\}\\}Issue: \\{\\{issue\\}\\}
Issue count: \\{\\{count\\}\\}
\\{\\{#details\\}\\}Issue details: \\{\\{details\\}\\}
\\{\\{/details\\}\\}\\{\\{#first_occurrence\\}\\}First occurrence: \\{\\{first_occurrence\\}\\}
\\{\\{/first_occurrence\\}\\}
\\{\\{/issues\\}\\}\\{\\{#failure_reason\\}\\}Failure reason: \\{\\{failure_reason\\}\\}
\\{\\{/failure_reason\\}\\}\\{\\{#notification_message\\}\\}Notification message: \\{\\{notification_message\\}\\}
\\{\\{/notification_message\\}\\}\\{\\{#node_name\\}\\}Node name: \\{\\{node_name\\}\\}
\\{\\{/node_name\\}\\}\\{\\{#timestamp\\}\\}Timestamp: \\{\\{timestamp\\}\\}
\\{\\{/timestamp\\}\\}\\{\\{#error_messages\\}\\}Error message: \\{\\{message\\}\\}
\\{\\{/error_messages\\}\\}
\\{\\{/timestamp\\}\\}
\\{\\{/context.results\\}\\}
`,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { FC, useCallback } from 'react';
import React, { FC, useCallback, useMemo } from 'react';
import { EuiDescribedFormGroup, EuiForm, EuiFormRow, EuiSpacer, EuiSwitch } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';

Expand All @@ -22,10 +22,19 @@ interface TestsSelectionControlProps {
errors?: string[];
}

const disabledChecks = new Set<keyof Exclude<TransformHealthRuleTestsConfig, null | undefined>>([
'errorMessages',
]);

export const TestsSelectionControl: FC<TestsSelectionControlProps> = React.memo(
({ config, onChange, errors }) => {
const uiConfig = getResultTestConfig(config);

const initConfig = useMemo(() => {
return uiConfig;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const updateCallback = useCallback(
(update: Partial<Exclude<TransformHealthRuleTestsConfig, undefined>>) => {
onChange({
Expand All @@ -43,35 +52,37 @@ export const TestsSelectionControl: FC<TestsSelectionControlProps> = React.memo(
Object.entries(uiConfig) as Array<
[TransformHealthTests, typeof uiConfig[TransformHealthTests]]
>
).map(([name, conf], i) => {
return (
<EuiDescribedFormGroup
key={name}
title={<h4>{TRANSFORM_HEALTH_CHECK_NAMES[name]?.name}</h4>}
description={TRANSFORM_HEALTH_CHECK_NAMES[name]?.description}
fullWidth
gutterSize={'s'}
>
<EuiFormRow>
<EuiSwitch
label={
<FormattedMessage
id="xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel"
defaultMessage="Enable"
/>
}
onChange={updateCallback.bind(null, {
[name]: {
...uiConfig[name],
enabled: !uiConfig[name].enabled,
},
})}
checked={uiConfig[name].enabled}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
);
})}
)
.filter(([name]) => !disabledChecks.has(name) || initConfig[name].enabled)
.map(([name, conf], i) => {
return (
<EuiDescribedFormGroup
key={name}
title={<h4>{TRANSFORM_HEALTH_CHECK_NAMES[name]?.name}</h4>}
description={TRANSFORM_HEALTH_CHECK_NAMES[name]?.description}
fullWidth
gutterSize={'s'}
>
<EuiFormRow>
<EuiSwitch
label={
<FormattedMessage
id="xpack.transform.alertTypes.transformHealth.testsSelection.enableTestLabel"
defaultMessage="Enable"
/>
}
onChange={updateCallback.bind(null, {
[name]: {
...uiConfig[name],
enabled: !uiConfig[name].enabled,
},
})}
checked={uiConfig[name].enabled}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
);
})}
</EuiForm>
<EuiSpacer size="l" />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,28 @@ import type {
} from '@kbn/alerting-plugin/common';
import { RuleType } from '@kbn/alerting-plugin/server';
import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/server';
import { PLUGIN, TRANSFORM_RULE_TYPE } from '../../../../common/constants';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import { PLUGIN, type TransformHealth, TRANSFORM_RULE_TYPE } from '../../../../common/constants';
import { transformHealthRuleParams, TransformHealthRuleParams } from './schema';
import { transformHealthServiceProvider } from './transform_health_service';

export interface BaseResponse {
export interface BaseTransformAlertResponse {
transform_id: string;
description?: string;
health_status: TransformHealth;
issues?: Array<{ issue: string; details?: string; count: number; first_occurrence?: string }>;
}

export interface NotStartedTransformResponse extends BaseResponse {
export interface TransformStateReportResponse extends BaseTransformAlertResponse {
transform_state: string;
node_name?: string;
}

export interface ErrorMessagesTransformResponse extends BaseResponse {
export interface ErrorMessagesTransformResponse extends BaseTransformAlertResponse {
error_messages: Array<{ message: string; timestamp: number; node_name?: string }>;
}

export type TransformHealthResult = NotStartedTransformResponse | ErrorMessagesTransformResponse;
export type TransformHealthResult = TransformStateReportResponse | ErrorMessagesTransformResponse;

export type TransformHealthAlertContext = {
results: TransformHealthResult[];
Expand All @@ -54,14 +57,17 @@ export const TRANSFORM_ISSUE_DETECTED: ActionGroup<TransformIssue> = {
interface RegisterParams {
logger: Logger;
alerting: AlertingSetup;
getFieldFormatsStart: () => FieldFormatsStart;
}

export function registerTransformHealthRuleType(params: RegisterParams) {
const { alerting } = params;
alerting.registerType(getTransformHealthRuleType());
alerting.registerType(getTransformHealthRuleType(params.getFieldFormatsStart));
}

export function getTransformHealthRuleType(): RuleType<
export function getTransformHealthRuleType(
getFieldFormatsStart: () => FieldFormatsStart
): RuleType<
TransformHealthRuleParams,
never,
RuleTypeState,
Expand Down Expand Up @@ -105,14 +111,19 @@ export function getTransformHealthRuleType(): RuleType<
doesSetRecoveryContext: true,
async executor(options) {
const {
services: { scopedClusterClient, alertFactory },
services: { scopedClusterClient, alertFactory, uiSettingsClient },
params,
} = options;

const transformHealthService = transformHealthServiceProvider(
scopedClusterClient.asCurrentUser
const fieldFormatsRegistry = await getFieldFormatsStart().fieldFormatServiceFactory(
uiSettingsClient
);

const transformHealthService = transformHealthServiceProvider({
esClient: scopedClusterClient.asCurrentUser,
fieldFormatsRegistry,
});

const executionResult = await transformHealthService.getHealthChecksResults(params);

const unhealthyTests = executionResult.filter(({ isHealthy }) => !isHealthy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const transformHealthRuleParams = schema.object({
})
),
errorMessages: schema.nullable(
schema.object({
enabled: schema.boolean({ defaultValue: false }),
})
),
healthCheck: schema.nullable(
schema.object({
enabled: schema.boolean({ defaultValue: true }),
})
Expand Down
Loading

0 comments on commit d339b75

Please sign in to comment.