mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Adding duration configuration to Stack Monitoring Cluster Health rule (#147565)
## Summary This PR fixes #145843 by adding the ability to configure `duration` for the Stack Monitoring "Legacy Rules" along with a set of default rule parameters and custom validation; which can be configured per rule. There are three new attributes added the the LEGACY_RULE_DETAILS object: - `defaults` – The default values for parameters (so we can set the default duration to `2m` for Cluster Health) - `expressionConfig` – Configuration for turning on/off UI elements (like duration) - `validate` – A custom validate function (so we can ensure `duration` is provided) This will also allow us control over which of the legacy rules gets duration and which ones we want to keep "as is". It also makes room for adding additional UI features in the future. <img width="618" alt="image" src="https://user-images.githubusercontent.com/41702/207685736-d8dc3023-66d0-4e40-a564-830f290ec1e1.png"> ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [X] [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
c6af65a391
commit
747704544f
10 changed files with 114 additions and 39 deletions
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CommonAlertParamDetail } from './types/alerts';
|
||||
import { CommonAlertParamDetail, ExpressionConfig } from './types/alerts';
|
||||
import { AlertParamType } from './enums';
|
||||
import { validateDuration } from './validate_duration';
|
||||
|
||||
/**
|
||||
* Helper string to add as a tag in every logging call
|
||||
|
@ -252,10 +253,18 @@ export const RULE_THREAD_POOL_WRITE_REJECTIONS = `${RULE_PREFIX}alert_thread_poo
|
|||
export const RULE_CCR_READ_EXCEPTIONS = `${RULE_PREFIX}ccr_read_exceptions`;
|
||||
export const RULE_LARGE_SHARD_SIZE = `${RULE_PREFIX}shard_size`;
|
||||
|
||||
interface LegacyRuleDetails {
|
||||
label: string;
|
||||
description: string;
|
||||
defaults?: Record<string, unknown>;
|
||||
expressionConfig?: ExpressionConfig;
|
||||
validate?: (input: any) => { errors: {} };
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy rules details/label for server and public use
|
||||
*/
|
||||
export const LEGACY_RULE_DETAILS = {
|
||||
export const LEGACY_RULE_DETAILS: Record<string, LegacyRuleDetails> = {
|
||||
[RULE_CLUSTER_HEALTH]: {
|
||||
label: i18n.translate('xpack.monitoring.alerts.clusterHealth.label', {
|
||||
defaultMessage: 'Cluster health',
|
||||
|
@ -263,6 +272,13 @@ export const LEGACY_RULE_DETAILS = {
|
|||
description: i18n.translate('xpack.monitoring.alerts.clusterHealth.description', {
|
||||
defaultMessage: 'Alert when the health of the cluster changes.',
|
||||
}),
|
||||
defaults: {
|
||||
duration: '2m',
|
||||
},
|
||||
expressionConfig: {
|
||||
showDuration: true,
|
||||
},
|
||||
validate: validateDuration,
|
||||
},
|
||||
[RULE_ELASTICSEARCH_VERSION_MISMATCH]: {
|
||||
label: i18n.translate('xpack.monitoring.alerts.elasticsearchVersionMismatch.label', {
|
||||
|
|
|
@ -286,3 +286,7 @@ export interface AlertVersions {
|
|||
ccs?: string;
|
||||
versions: string[];
|
||||
}
|
||||
|
||||
export interface ExpressionConfig {
|
||||
showDuration?: boolean;
|
||||
}
|
||||
|
|
28
x-pack/plugins/monitoring/common/validate_duration.ts
Normal file
28
x-pack/plugins/monitoring/common/validate_duration.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { RuleTypeParams, ValidationResult } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
export interface ValidateDurationOptions extends RuleTypeParams {
|
||||
duration: string;
|
||||
}
|
||||
|
||||
export const validateDuration = (inputValues: ValidateDurationOptions): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors: { [key: string]: string[] } = {
|
||||
duration: [],
|
||||
};
|
||||
if (!inputValues.duration) {
|
||||
errors.duration.push(
|
||||
i18n.translate('xpack.monitoring.alerts.validation.duration', {
|
||||
defaultMessage: 'A valid duration is required.',
|
||||
})
|
||||
);
|
||||
}
|
||||
validationResult.errors = errors;
|
||||
return validationResult;
|
||||
};
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import type { RuleTypeParams } from '@kbn/alerting-plugin/common';
|
||||
import type { RuleTypeModel, ValidationResult } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { validateDuration, ValidateDurationOptions } from '../../../common/validate_duration';
|
||||
import {
|
||||
RULE_CCR_READ_EXCEPTIONS,
|
||||
RULE_DETAILS,
|
||||
|
@ -20,29 +19,9 @@ import {
|
|||
LazyExpressionProps,
|
||||
} from '../components/param_details_form/lazy_expression';
|
||||
|
||||
interface ValidateOptions extends RuleTypeParams {
|
||||
duration: string;
|
||||
}
|
||||
|
||||
const validate = (inputValues: ValidateOptions): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
const errors: { [key: string]: string[] } = {
|
||||
duration: [],
|
||||
};
|
||||
if (!inputValues.duration) {
|
||||
errors.duration.push(
|
||||
i18n.translate('xpack.monitoring.alerts.validation.duration', {
|
||||
defaultMessage: 'A valid duration is required.',
|
||||
})
|
||||
);
|
||||
}
|
||||
validationResult.errors = errors;
|
||||
return validationResult;
|
||||
};
|
||||
|
||||
export function createCCRReadExceptionsAlertType(
|
||||
config: MonitoringConfig
|
||||
): RuleTypeModel<ValidateOptions> {
|
||||
): RuleTypeModel<ValidateDurationOptions> {
|
||||
return {
|
||||
id: RULE_CCR_READ_EXCEPTIONS,
|
||||
description: RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].description,
|
||||
|
@ -57,7 +36,7 @@ export function createCCRReadExceptionsAlertType(
|
|||
paramDetails={RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].paramDetails}
|
||||
/>
|
||||
),
|
||||
validate,
|
||||
validate: validateDuration,
|
||||
defaultActionMessage: '{{context.internalFullMessage}}',
|
||||
requiresAppContext: RULE_REQUIRES_APP_CONTEXT,
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui';
|
|||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { debounce } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CommonAlertParamDetails } from '../../../../common/types/alerts';
|
||||
import { CommonAlertParamDetails, ExpressionConfig } from '../../../../common/types/alerts';
|
||||
import { AlertParamDuration } from '../../flyout_expressions/alert_param_duration';
|
||||
import { AlertParamType } from '../../../../common/enums';
|
||||
import { AlertParamPercentage } from '../../flyout_expressions/alert_param_percentage';
|
||||
|
@ -31,6 +31,8 @@ export interface Props {
|
|||
paramDetails: CommonAlertParamDetails;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
config?: MonitoringConfig;
|
||||
defaults?: Record<string, unknown>;
|
||||
expressionConfig?: ExpressionConfig;
|
||||
}
|
||||
|
||||
export const Expression: React.FC<Props> = (props) => {
|
||||
|
|
|
@ -12,10 +12,19 @@ import { useDerivedIndexPattern } from '../components/param_details_form/use_der
|
|||
import { convertKueryToElasticSearchQuery } from '../../lib/kuery';
|
||||
import { KueryBar } from '../../components/kuery_bar';
|
||||
import { Props } from '../components/param_details_form/expression';
|
||||
import { AlertParamDuration } from '../flyout_expressions/alert_param_duration';
|
||||
|
||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||
|
||||
export const Expression = ({ ruleParams, config, setRuleParams, dataViews }: Props) => {
|
||||
export const Expression = ({
|
||||
ruleParams,
|
||||
config,
|
||||
setRuleParams,
|
||||
dataViews,
|
||||
errors,
|
||||
defaults,
|
||||
expressionConfig,
|
||||
}: Props) => {
|
||||
const { derivedIndexPattern } = useDerivedIndexPattern(dataViews, config);
|
||||
const onFilterChange = useCallback(
|
||||
(filter: string) => {
|
||||
|
@ -45,8 +54,26 @@ export const Expression = ({ ruleParams, config, setRuleParams, dataViews }: Pro
|
|||
<></>
|
||||
);
|
||||
|
||||
const duration = expressionConfig?.showDuration ? (
|
||||
<EuiFormRow>
|
||||
<AlertParamDuration
|
||||
key="duration"
|
||||
name={'duration'}
|
||||
duration={ruleParams.duration ?? defaults?.duration}
|
||||
label={i18n.translate('xpack.monitoring.alerts.legacy.paramDetails.duration.label', {
|
||||
defaultMessage: 'In the last',
|
||||
})}
|
||||
errors={errors.duration}
|
||||
setRuleParams={setRuleParams}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiForm component="form">
|
||||
{duration}
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.monitoring.alerts.filterLable', {
|
||||
|
|
|
@ -15,8 +15,11 @@ import {
|
|||
import type { MonitoringConfig } from '../../types';
|
||||
import { LazyExpression, LazyExpressionProps } from './lazy_expression';
|
||||
|
||||
const DEFAULT_VALIDATE = () => ({ errors: {} });
|
||||
|
||||
export function createLegacyAlertTypes(config: MonitoringConfig): RuleTypeModel[] {
|
||||
return LEGACY_RULES.map((legacyAlert) => {
|
||||
const validate = LEGACY_RULE_DETAILS[legacyAlert].validate ?? DEFAULT_VALIDATE;
|
||||
return {
|
||||
id: legacyAlert,
|
||||
description: LEGACY_RULE_DETAILS[legacyAlert].description,
|
||||
|
@ -25,10 +28,15 @@ export function createLegacyAlertTypes(config: MonitoringConfig): RuleTypeModel[
|
|||
return `${docLinks.links.monitoring.alertsKibanaClusterAlerts}`;
|
||||
},
|
||||
ruleParamsExpression: (props: LazyExpressionProps) => (
|
||||
<LazyExpression {...props} config={config} />
|
||||
<LazyExpression
|
||||
{...props}
|
||||
defaults={LEGACY_RULE_DETAILS[legacyAlert].defaults}
|
||||
expressionConfig={LEGACY_RULE_DETAILS[legacyAlert].expressionConfig}
|
||||
config={config}
|
||||
/>
|
||||
),
|
||||
defaultActionMessage: '{{context.internalFullMessage}}',
|
||||
validate: () => ({ errors: {} }),
|
||||
validate,
|
||||
requiresAppContext: RULE_REQUIRES_APP_CONTEXT,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -61,7 +61,12 @@ export class ClusterHealthRule extends BaseRule {
|
|||
esClient: ElasticsearchClient,
|
||||
clusters: AlertCluster[]
|
||||
): Promise<AlertData[]> {
|
||||
const healths = await fetchClusterHealth(esClient, clusters, params.filterQuery);
|
||||
const healths = await fetchClusterHealth(
|
||||
esClient,
|
||||
clusters,
|
||||
params.filterQuery,
|
||||
params.duration
|
||||
);
|
||||
return healths.map((clusterHealth) => {
|
||||
const shouldFire = clusterHealth.health !== AlertClusterHealthType.Green;
|
||||
const severity =
|
||||
|
|
|
@ -55,10 +55,15 @@ describe('fetchClusterHealth', () => {
|
|||
});
|
||||
it('should call ES with correct query', async () => {
|
||||
const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
await fetchClusterHealth(esClient, [
|
||||
{ clusterUuid: '1', clusterName: 'foo1' },
|
||||
{ clusterUuid: '2', clusterName: 'foo2' },
|
||||
]);
|
||||
await fetchClusterHealth(
|
||||
esClient,
|
||||
[
|
||||
{ clusterUuid: '1', clusterName: 'foo1' },
|
||||
{ clusterUuid: '2', clusterName: 'foo2' },
|
||||
],
|
||||
undefined,
|
||||
'1h'
|
||||
);
|
||||
expect(esClient.search).toHaveBeenCalledWith({
|
||||
index:
|
||||
'*:.monitoring-es-*,.monitoring-es-*,*:metrics-elasticsearch.stack_monitoring.cluster_stats-*,metrics-elasticsearch.stack_monitoring.cluster_stats-*',
|
||||
|
@ -90,7 +95,7 @@ describe('fetchClusterHealth', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{ range: { timestamp: { gte: 'now-2m' } } },
|
||||
{ range: { timestamp: { gte: 'now-1h' } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -15,7 +15,8 @@ import { CCS_REMOTE_PATTERN } from '../../../common/constants';
|
|||
export async function fetchClusterHealth(
|
||||
esClient: ElasticsearchClient,
|
||||
clusters: AlertCluster[],
|
||||
filterQuery?: string
|
||||
filterQuery?: string,
|
||||
duration: string = '2m'
|
||||
): Promise<AlertClusterHealth[]> {
|
||||
const indexPatterns = getIndexPatterns({
|
||||
config: Globals.app.config,
|
||||
|
@ -58,7 +59,7 @@ export async function fetchClusterHealth(
|
|||
{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: 'now-2m',
|
||||
gte: `now-${duration}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue