Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Monitoring] Add KQL filter bar to alerts #111663

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import { isFunction, get } from 'lodash';
import type { MonitoringConfig } from '../config';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { MonitoringConfig } from '../server/config';

type Config = Partial<MonitoringConfig> & {
get?: (key: string) => any;
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/monitoring/common/types/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,16 @@ export interface CommonAlertParams {
duration: string;
threshold?: number;
limit?: string;
filterQuery?: string;
filterQueryText?: string;
[key: string]: unknown;
}

export interface ThreadPoolRejectionsAlertParams {
threshold: number;
duration: string;
filterQuery?: string;
filterQueryText?: string;
}

export interface AlertEnableAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RULE_REQUIRES_APP_CONTEXT,
} from '../../../common/constants';
import { AlertTypeParams } from '../../../../alerting/common';
import { MonitoringConfig } from '../../types';

interface ValidateOptions extends AlertTypeParams {
duration: string;
Expand All @@ -36,7 +37,9 @@ const validate = (inputValues: ValidateOptions): ValidationResult => {
return validationResult;
};

export function createCCRReadExceptionsAlertType(): AlertTypeModel<ValidateOptions> {
export function createCCRReadExceptionsAlertType(
config: MonitoringConfig
): AlertTypeModel<ValidateOptions> {
return {
id: RULE_CCR_READ_EXCEPTIONS,
description: RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].description,
Expand All @@ -45,7 +48,11 @@ export function createCCRReadExceptionsAlertType(): AlertTypeModel<ValidateOptio
return `${docLinks.links.monitoring.alertsKibanaCCRReadExceptions}`;
},
alertParamsExpression: (props: Props) => (
<Expression {...props} paramDetails={RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].paramDetails} />
<Expression
{...props}
config={config}
paramDetails={RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].paramDetails}
/>
),
validate,
defaultActionMessage: '{{context.internalFullMessage}}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,38 @@
* 2.0.
*/

import React, { Fragment } from 'react';
import { EuiForm, EuiSpacer } from '@elastic/eui';
import React, { Fragment, useCallback } from 'react';
import { EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { debounce } from 'lodash';
import { i18n } from '@kbn/i18n';
import { CommonAlertParamDetails } 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';
import { AlertParamNumber } from '../../flyout_expressions/alert_param_number';
import { AlertParamTextField } from '../../flyout_expressions/alert_param_textfield';
import { MonitoringConfig } from '../../../types';
import { useDerivedIndexPattern } from './use_derived_index_pattern';
import { KueryBar } from '../../../components/kuery_bar';
import { convertKueryToElasticSearchQuery } from '../../../lib/kuery';

const FILTER_TYPING_DEBOUNCE_MS = 500;

export interface Props {
alertParams: { [property: string]: any };
setAlertParams: (property: string, value: any) => void;
setAlertProperty: (property: string, value: any) => void;
errors: { [key: string]: string[] };
paramDetails: CommonAlertParamDetails;
data: DataPublicPluginStart;
config?: MonitoringConfig;
}

export const Expression: React.FC<Props> = (props) => {
const { alertParams, paramDetails, setAlertParams, errors } = props;
const { alertParams, paramDetails, setAlertParams, errors, config, data } = props;

const { derivedIndexPattern } = useDerivedIndexPattern(data, config);

const alertParamsUi = Object.keys(paramDetails).map((alertParamName) => {
const details = paramDetails[alertParamName];
Expand Down Expand Up @@ -77,10 +90,44 @@ export const Expression: React.FC<Props> = (props) => {
}
});

const onFilterChange = useCallback(
(filter: string) => {
setAlertParams('filterQueryText', filter);
setAlertParams(
'filterQuery',
convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || ''
);
},
[setAlertParams, derivedIndexPattern]
);

/* eslint-disable-next-line react-hooks/exhaustive-deps */
const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [
onFilterChange,
]);

return (
<Fragment>
<EuiForm component="form">{alertParamsUi}</EuiForm>
<EuiSpacer />
<EuiForm component="form">
{alertParamsUi}
<EuiSpacer />
<EuiFormRow
label={i18n.translate('xpack.monitoring.alerts.filterLable', {
defaultMessage: 'Filter',
})}
helpText={i18n.translate('xpack.monitoring.alerts.filterHelpText', {
defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.',
})}
>
<KueryBar
value={alertParams.filterQueryText}
derivedIndexPattern={derivedIndexPattern}
onSubmit={onFilterChange}
onChange={debouncedOnFilterChange}
/>
</EuiFormRow>
<EuiSpacer />
</EuiForm>
</Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { useEffect, useState } from 'react';
import { DataPublicPluginStart, IFieldType, IIndexPattern } from 'src/plugins/data/public';
import {
INDEX_PATTERN_BEATS,
INDEX_PATTERN_ELASTICSEARCH,
INDEX_PATTERN_KIBANA,
INDEX_PATTERN_LOGSTASH,
} from '../../../../common/constants';
import { prefixIndexPattern } from '../../../../common/ccs_utils';
import { MonitoringConfig } from '../../../types';

const INDEX_PATTERNS = `${INDEX_PATTERN_ELASTICSEARCH},${INDEX_PATTERN_KIBANA},${INDEX_PATTERN_LOGSTASH},${INDEX_PATTERN_BEATS}`;

export const useDerivedIndexPattern = (
data: DataPublicPluginStart,
config?: MonitoringConfig
): { loading: boolean; derivedIndexPattern: IIndexPattern } => {
const indexPattern = prefixIndexPattern(config || ({} as MonitoringConfig), INDEX_PATTERNS, '*');
const [loading, setLoading] = useState<boolean>(true);
const [fields, setFields] = useState<IFieldType[]>([]);
useEffect(() => {
(async function fetchData() {
const result = await data.indexPatterns.getFieldsForWildcard({
pattern: indexPattern,
});
setFields(result);
setLoading(false);
})();
}, [indexPattern, data.indexPatterns]);
return {
loading,
derivedIndexPattern: {
title: indexPattern,
fields,
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
import { RULE_CPU_USAGE, RULE_DETAILS, RULE_REQUIRES_APP_CONTEXT } from '../../../common/constants';
import { validate, MonitoringAlertTypeParams } from '../components/param_details_form/validation';
import { Expression, Props } from '../components/param_details_form/expression';
import { MonitoringConfig } from '../../types';

export function createCpuUsageAlertType(): AlertTypeModel<MonitoringAlertTypeParams> {
export function createCpuUsageAlertType(
config: MonitoringConfig
): AlertTypeModel<MonitoringAlertTypeParams> {
return {
id: RULE_CPU_USAGE,
description: RULE_DETAILS[RULE_CPU_USAGE].description,
Expand All @@ -21,7 +24,11 @@ export function createCpuUsageAlertType(): AlertTypeModel<MonitoringAlertTypePar
return `${docLinks.links.monitoring.alertsKibanaCpuThreshold}`;
},
alertParamsExpression: (props: Props) => (
<Expression {...props} paramDetails={RULE_DETAILS[RULE_CPU_USAGE].paramDetails} />
<Expression
{...props}
config={config}
paramDetails={RULE_DETAILS[RULE_CPU_USAGE].paramDetails}
/>
),
validate,
defaultActionMessage: '{{context.internalFullMessage}}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import {
RULE_DETAILS,
RULE_REQUIRES_APP_CONTEXT,
} from '../../../common/constants';
import { MonitoringConfig } from '../../types';

export function createDiskUsageAlertType(): AlertTypeModel<MonitoringAlertTypeParams> {
export function createDiskUsageAlertType(
config: MonitoringConfig
): AlertTypeModel<MonitoringAlertTypeParams> {
return {
id: RULE_DISK_USAGE,
description: RULE_DETAILS[RULE_DISK_USAGE].description,
Expand All @@ -26,7 +29,11 @@ export function createDiskUsageAlertType(): AlertTypeModel<MonitoringAlertTypePa
return `${docLinks.links.monitoring.alertsKibanaDiskThreshold}`;
},
alertParamsExpression: (props: Props) => (
<Expression {...props} paramDetails={RULE_DETAILS[RULE_DISK_USAGE].paramDetails} />
<Expression
{...props}
config={config}
paramDetails={RULE_DETAILS[RULE_DISK_USAGE].paramDetails}
/>
),
validate,
defaultActionMessage: '{{context.internalFullMessage}}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RULE_REQUIRES_APP_CONTEXT,
} from '../../../common/constants';
import { AlertTypeParams } from '../../../../alerting/common';
import { MonitoringConfig } from '../../types';

interface ValidateOptions extends AlertTypeParams {
indexPattern: string;
Expand All @@ -36,7 +37,9 @@ const validate = (inputValues: ValidateOptions): ValidationResult => {
return validationResult;
};

export function createLargeShardSizeAlertType(): AlertTypeModel<ValidateOptions> {
export function createLargeShardSizeAlertType(
config: MonitoringConfig
): AlertTypeModel<ValidateOptions> {
return {
id: RULE_LARGE_SHARD_SIZE,
description: RULE_DETAILS[RULE_LARGE_SHARD_SIZE].description,
Expand All @@ -45,7 +48,11 @@ export function createLargeShardSizeAlertType(): AlertTypeModel<ValidateOptions>
return `${docLinks.links.monitoring.alertsKibanaLargeShardSize}`;
},
alertParamsExpression: (props: Props) => (
<Expression {...props} paramDetails={RULE_DETAILS[RULE_LARGE_SHARD_SIZE].paramDetails} />
<Expression
{...props}
config={config}
paramDetails={RULE_DETAILS[RULE_LARGE_SHARD_SIZE].paramDetails}
/>
),
validate,
defaultActionMessage: '{{context.internalFullMessage}}',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 } from 'react';
import { debounce } from 'lodash';
import { EuiSpacer, EuiForm, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useDerivedIndexPattern } from '../components/param_details_form/use_derived_index_pattern';
import { convertKueryToElasticSearchQuery } from '../../lib/kuery';
import { KueryBar } from '../../components/kuery_bar';
import { Props } from '../components/param_details_form/expression';

const FILTER_TYPING_DEBOUNCE_MS = 500;

export const Expression = ({ alertParams, config, setAlertParams, data }: Props) => {
const { derivedIndexPattern } = useDerivedIndexPattern(data, config);
const onFilterChange = useCallback(
(filter: string) => {
setAlertParams('filterQueryText', filter);
setAlertParams(
'filterQuery',
convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || ''
);
},
[setAlertParams, derivedIndexPattern]
);

/* eslint-disable-next-line react-hooks/exhaustive-deps */
const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [
onFilterChange,
]);
return (
<EuiForm component="form">
<EuiFormRow
fullWidth
label={i18n.translate('xpack.monitoring.alerts.filterLable', {
defaultMessage: 'Filter',
})}
helpText={i18n.translate('xpack.monitoring.alerts.filterHelpText', {
defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.',
})}
>
<KueryBar
value={alertParams.filterQueryText}
derivedIndexPattern={derivedIndexPattern}
onSubmit={onFilterChange}
onChange={debouncedOnFilterChange}
/>
</EuiFormRow>
<EuiSpacer />
</EuiForm>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
* 2.0.
*/

import React, { Fragment } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiTextColor, EuiSpacer } from '@elastic/eui';
import React from 'react';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
import {
LEGACY_RULES,
LEGACY_RULE_DETAILS,
RULE_REQUIRES_APP_CONTEXT,
} from '../../../common/constants';
import { MonitoringConfig } from '../../types';
import { Expression } from './expression';
import { Props } from '../components/param_details_form/expression';

export function createLegacyAlertTypes(): AlertTypeModel[] {
export function createLegacyAlertTypes(config: MonitoringConfig): AlertTypeModel[] {
return LEGACY_RULES.map((legacyAlert) => {
return {
id: legacyAlert,
Expand All @@ -25,17 +26,7 @@ export function createLegacyAlertTypes(): AlertTypeModel[] {
documentationUrl(docLinks) {
return `${docLinks.links.monitoring.alertsKibanaClusterAlerts}`;
},
alertParamsExpression: () => (
<Fragment>
<EuiSpacer />
<EuiTextColor color="subdued">
{i18n.translate('xpack.monitoring.alerts.legacyAlert.expressionText', {
defaultMessage: 'There is nothing to configure.',
})}
</EuiTextColor>
<EuiSpacer />
</Fragment>
),
alertParamsExpression: (props: Props) => <Expression {...props} config={config} />,
defaultActionMessage: '{{context.internalFullMessage}}',
validate: () => ({ errors: {} }),
requiresAppContext: RULE_REQUIRES_APP_CONTEXT,
Expand Down
Loading