Skip to content

Commit

Permalink
[Alert details page] Use alert rule params instead of rule params in …
Browse files Browse the repository at this point in the history
…the custom threshold app section (#197023)

Related to #181828

## Summary

This PR refactors the custom threshold app section to rely on the alert
rule params instead of rule params.

### How to test
- Create a custom threshold rule and verify that the alert details page
works as before
- Also, check the log rate analysis component and ensure it works as
before

(cherry picked from commit c0393ae)
  • Loading branch information
maryam-saeidi committed Oct 22, 2024
1 parent 1ccf86f commit 6cf5c4c
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React from 'react';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
Expand Down Expand Up @@ -85,8 +86,10 @@ describe('AlertDetailsAppSection', () => {
<IntlProvider locale="en">
<QueryClientProvider client={queryClient}>
<AlertDetailsAppSection
alert={buildCustomThresholdAlert(alert, alertFields)}
rule={buildCustomThresholdRule()}
alert={buildCustomThresholdAlert(alert, {
[ALERT_RULE_PARAMETERS]: buildCustomThresholdRule().params,
...alertFields,
})}
/>
</QueryClientProvider>
</IntlProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ import {
useEuiTheme,
transparentize,
} from '@elastic/eui';
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils';
import {
ALERT_END,
ALERT_START,
ALERT_EVALUATION_VALUES,
ALERT_GROUP,
ALERT_RULE_PARAMETERS,
} from '@kbn/rule-data-utils';
import { DataView } from '@kbn/data-views-plugin/common';
import type {
EventAnnotationConfig,
Expand All @@ -36,9 +41,8 @@ import { getGroupFilters } from '../../../../../common/custom_threshold_rule/hel
import { useLicense } from '../../../../hooks/use_license';
import { useKibana } from '../../../../utils/kibana_react';
import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter';
import { AlertParams } from '../../types';
import { Threshold } from '../threshold';
import { CustomThresholdRule, CustomThresholdAlert } from '../types';
import { CustomThresholdAlert } from '../types';
import { LogRateAnalysis } from './log_rate_analysis';
import { RuleConditionChart } from '../../../rule_condition_chart/rule_condition_chart';
import { getViewInAppUrl } from '../../../../../common/custom_threshold_rule/get_view_in_app_url';
Expand All @@ -47,11 +51,10 @@ import { generateChartTitleAndTooltip } from './helpers/generate_chart_title_and

interface AppSectionProps {
alert: CustomThresholdAlert;
rule: CustomThresholdRule;
}

// eslint-disable-next-line import/no-default-export
export default function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
export default function AlertDetailsAppSection({ alert }: AppSectionProps) {
const services = useKibana().services;
const {
charts,
Expand All @@ -66,10 +69,10 @@ export default function AlertDetailsAppSection({ alert, rule }: AppSectionProps)
const [dataView, setDataView] = useState<DataView>();
const [, setDataViewError] = useState<Error>();
const [timeRange, setTimeRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' });
const ruleParams = rule.params as RuleTypeParams & AlertParams;
const chartProps = {
baseTheme: charts.theme.useChartsBaseTheme(),
};
const ruleParams = alert.fields[ALERT_RULE_PARAMETERS];
const alertStart = alert.fields[ALERT_START];
const alertEnd = alert.fields[ALERT_END];
const groups = alert.fields[ALERT_GROUP];
Expand Down Expand Up @@ -213,7 +216,7 @@ export default function AlertDetailsAppSection({ alert, rule }: AppSectionProps)
);
})}
{hasLogRateAnalysisLicense && (
<LogRateAnalysis alert={alert} dataView={dataView} rule={rule} services={services} />
<LogRateAnalysis alert={alert} dataView={dataView} services={services} />
)}
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { COMPARATORS } from '@kbn/alerting-comparators';
import { ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { Aggregators } from '../../../../../../common/custom_threshold_rule/types';
import { CustomThresholdRuleTypeParams } from '../../../types';
import { getLogRateAnalysisEQQuery } from './log_rate_analysis_query';
Expand Down Expand Up @@ -50,74 +51,83 @@ describe('buildEsQuery', () => {
};
const testData: Array<{
title: string;
params: CustomThresholdRuleTypeParams;
alert: any;
}> = [
{
title: 'rule with optional filer, count filter and group by',
params: mockedParams,
alert: {
fields: {
'kibana.alert.group': [mockedAlertWithMultipleGroups.fields['kibana.alert.group'][0]],
[ALERT_RULE_PARAMETERS]: mockedParams,
},
},
},
{
title: 'rule with optional filer, count filter and multiple group by',
params: mockedParams,
alert: mockedAlertWithMultipleGroups,
alert: {
fields: {
...mockedAlertWithMultipleGroups.fields,
[ALERT_RULE_PARAMETERS]: mockedParams,
},
},
},
{
title: 'rule with optional filer, count filter and WITHOUT group by',
params: mockedParams,
alert: {},
alert: {
fields: {
[ALERT_RULE_PARAMETERS]: mockedParams,
},
},
},
{
title: 'rule without filter and with group by',
params: {
groupBy: ['host.hostname'],
searchConfiguration: {
index,
query: { query: '', language: 'kuery' },
},
criteria: [
{
metrics: [{ name: 'A', aggType: Aggregators.COUNT }],
timeSize: 1,
timeUnit: 'm',
threshold: [90],
comparator: COMPARATORS.GREATER_THAN,
},
],
},
alert: {
fields: {
'kibana.alert.group': [mockedAlertWithMultipleGroups.fields['kibana.alert.group'][0]],
[ALERT_RULE_PARAMETERS]: {
groupBy: ['host.hostname'],
searchConfiguration: {
index,
query: { query: '', language: 'kuery' },
},
criteria: [
{
metrics: [{ name: 'A', aggType: Aggregators.COUNT }],
timeSize: 1,
timeUnit: 'm',
threshold: [90],
comparator: COMPARATORS.GREATER_THAN,
},
],
},
},
},
},
{
title: 'rule with multiple metrics',
params: {
...mockedParams,
criteria: [
{
metrics: [
{ name: 'A', aggType: Aggregators.COUNT, filter: 'host.name: host-1' },
{ name: 'B', aggType: Aggregators.AVERAGE, field: 'system.load.1' },
alert: {
fields: {
[ALERT_RULE_PARAMETERS]: {
...mockedParams,
criteria: [
{
metrics: [
{ name: 'A', aggType: Aggregators.COUNT, filter: 'host.name: host-1' },
{ name: 'B', aggType: Aggregators.AVERAGE, field: 'system.load.1' },
],
timeSize: 1,
timeUnit: 'm',
threshold: [90],
comparator: COMPARATORS.GREATER_THAN,
},
],
timeSize: 1,
timeUnit: 'm',
threshold: [90],
comparator: COMPARATORS.GREATER_THAN,
},
],
},
},
alert: {},
},
];

test.each(testData)('should generate correct es query for $title', ({ alert, params }) => {
expect(getLogRateAnalysisEQQuery(alert, params)).toMatchSnapshot();
test.each(testData)('should generate correct es query for $title', ({ alert }) => {
expect(getLogRateAnalysisEQQuery(alert)).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import { get } from 'lodash';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { CustomThresholdAlert } from '../../types';
import { getGroupFilters } from '../../../../../../common/custom_threshold_rule/helpers/get_group';
import { Aggregators } from '../../../../../../common/custom_threshold_rule/types';
import { buildEsQuery } from '../../../../../utils/build_es_query';
import type { CustomThresholdExpressionMetric } from '../../../../../../common/custom_threshold_rule/types';
import type { TopAlert } from '../../../../../typings/alerts';
import type { CustomThresholdRuleTypeParams } from '../../../types';
import { Group } from '../../../../../../common/typings';

const getKuery = (metrics: CustomThresholdExpressionMetric[], filter?: string) => {
Expand All @@ -32,23 +32,23 @@ const getKuery = (metrics: CustomThresholdExpressionMetric[], filter?: string) =
};

export const getLogRateAnalysisEQQuery = (
alert: TopAlert<Record<string, any>>,
params: CustomThresholdRuleTypeParams
alert: CustomThresholdAlert
): QueryDslQueryContainer | undefined => {
const ruleParams = alert.fields[ALERT_RULE_PARAMETERS];
// We only show log rate analysis for one condition with one count aggregation
if (
params.criteria.length !== 1 ||
params.criteria[0].metrics.length !== 1 ||
params.criteria[0].metrics[0].aggType !== Aggregators.COUNT
ruleParams.criteria.length !== 1 ||
ruleParams.criteria[0].metrics.length !== 1 ||
ruleParams.criteria[0].metrics[0].aggType !== Aggregators.COUNT
) {
return;
}

const group = get(alert, 'fields["kibana.alert.group"]') as Group[] | undefined;
const optionalFilter = get(params.searchConfiguration, 'query.query') as string | undefined;
const optionalFilter = get(ruleParams.searchConfiguration, 'query.query') as string | undefined;
const groupByFilters = getGroupFilters(group);
const boolQuery = buildEsQuery({
kuery: getKuery(params.criteria[0].metrics, optionalFilter),
kuery: getKuery(ruleParams.criteria[0].metrics, optionalFilter),
filters: groupByFilters,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,14 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { Message } from '@kbn/observability-ai-assistant-plugin/public';
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import { ALERT_END } from '@kbn/rule-data-utils';
import { CustomThresholdRuleTypeParams } from '../../types';
import { TopAlert } from '../../../..';
import { ALERT_END, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { CustomThresholdAlert } from '../types';
import { Color, colorTransformer } from '../../../../../common/custom_threshold_rule/color_palette';
import { getLogRateAnalysisEQQuery } from './helpers/log_rate_analysis_query';

export interface AlertDetailsLogRateAnalysisProps {
alert: TopAlert<Record<string, any>>;
alert: CustomThresholdAlert;
dataView: any;
rule: Rule<CustomThresholdRuleTypeParams>;
services: any;
}

Expand All @@ -40,12 +37,7 @@ interface SignificantFieldValue {
pValue: number | null;
}

export function LogRateAnalysis({
alert,
dataView,
rule,
services,
}: AlertDetailsLogRateAnalysisProps) {
export function LogRateAnalysis({ alert, dataView, services }: AlertDetailsLogRateAnalysisProps) {
const {
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
Expand All @@ -57,22 +49,23 @@ export function LogRateAnalysis({
| { logRateAnalysisType: LogRateAnalysisType; significantFieldValues: SignificantFieldValue[] }
| undefined
>();
const ruleParams = alert.fields[ALERT_RULE_PARAMETERS];

useEffect(() => {
const esSearchRequest = getLogRateAnalysisEQQuery(alert, rule.params);
const esSearchRequest = getLogRateAnalysisEQQuery(alert);

if (esSearchRequest) {
setEsSearchQuery(esSearchRequest);
}
}, [alert, rule.params]);
}, [alert]);

const { timeRange, windowParameters } = useMemo(() => {
const alertStartedAt = moment(alert.start).toISOString();
const alertEndedAt = alert.fields[ALERT_END]
? moment(alert.fields[ALERT_END]).toISOString()
: undefined;
const timeSize = rule.params.criteria[0]?.timeSize as number | undefined;
const timeUnit = rule.params.criteria[0]?.timeUnit as
const timeSize = ruleParams.criteria[0]?.timeSize as number | undefined;
const timeUnit = ruleParams.criteria[0]?.timeUnit as
| moment.unitOfTime.DurationConstructor
| undefined;

Expand All @@ -82,7 +75,7 @@ export function LogRateAnalysis({
timeSize,
timeUnit,
});
}, [alert, rule]);
}, [alert.fields, alert.start, ruleParams.criteria]);

const logRateAnalysisTitle = i18n.translate(
'xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,23 @@ export const buildCustomThresholdAlert = (
{
name: 'B',
aggType: Aggregators.MAX,
metric: 'system.cpu.user.pct',
field: 'system.cpu.user.pct',
},
],
threshold: [4],
timeSize: 15,
timeUnit: 'm',
warningComparator: COMPARATORS.GREATER_THAN,
warningThreshold: [2.2],
},
],
sourceId: 'default',
alertOnNoData: true,
alertOnGroupDisappear: true,
searchConfiguration: {
query: {
query: '',
language: 'kuery',
},
index: 'b3eadf0e-1053-41d0-9672-dc1d7789dd68',
},
},
'kibana.alert.evaluation.values': [2500, 5],
'kibana.alert.group': [{ field: 'host.name', value: 'host-1' }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
import * as rt from 'io-ts';
import { CasesPublicStart } from '@kbn/cases-plugin/public';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public';
import { OsqueryPluginStart } from '@kbn/osquery-plugin/public';
import { ALERT_GROUP } from '@kbn/rule-data-utils';
import { ALERT_GROUP, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import {
Expand Down Expand Up @@ -87,11 +87,12 @@ export type RendererFunction<RenderArgs, Result = RendererResult> = (args: Rende

export interface CustomThresholdRuleTypeParams extends RuleTypeParams {
criteria: CustomMetricExpressionParams[];
searchConfiguration: SerializedSearchSourceFields;
searchConfiguration: CustomThresholdSearchSourceFields;
groupBy?: string | string[];
}
export interface CustomThresholdAlertFields {
[ALERT_GROUP]?: Array<{ field: string; value: string }>;
[ALERT_RULE_PARAMETERS]: CustomThresholdRuleTypeParams;
}

export const expressionTimestampsRT = rt.type({
Expand Down

0 comments on commit 6cf5c4c

Please sign in to comment.