[Alert details page][Custom threshold] Add history chart to custom threshold alert details page (#176513)

Closes #175200

## Summary

This PR adds a history chart to the custom threshold alert details page.
The history chart is only added if we have only 1 condition in the rule.

Also, this PR fixes the issue of not applying group by information on
the main chart that I mistakenly introduced during refactoring code in
this [PR](https://github.com/elastic/kibana/pull/175777).


![image](22b449c4-71de-4714-8ab8-9fdd244eb943)

## 🧪 How to test
- Create a custom threshold rule with only one condition
- Go to the alert details page from the alert table actions
- You should be able to see the history chart for the last 30 days with
the correct filtering both for optional KQL and group by information
This commit is contained in:
Maryam Saeidi 2024-02-13 12:55:15 +01:00 committed by GitHub
parent 873ae31687
commit 1aa5e3829e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 476 additions and 92 deletions

View file

@ -150,4 +150,61 @@ describe('useAlertsHistory', () => {
expect(result.current.data.histogramTriggeredAlerts?.length).toEqual(31);
expect(result.current.data.totalTriggeredAlerts).toEqual(32);
});
it('calls http post including term queries', async () => {
const controller = new AbortController();
const signal = controller.signal;
const mockedHttpPost = jest.fn();
const http = {
post: mockedHttpPost.mockResolvedValue({
hits: { total: { value: 32, relation: 'eq' }, max_score: null, hits: [] },
aggregations: {
avgTimeToRecoverUS: { doc_count: 28, recoveryTime: { value: 134959464.2857143 } },
histogramTriggeredAlerts: {
buckets: [
{ key_as_string: '2023-04-10T00:00:00.000Z', key: 1681084800000, doc_count: 0 },
],
},
},
}),
} as unknown as HttpSetup;
const { result, waitFor } = renderHook<useAlertsHistoryProps, UseAlertsHistory>(
() =>
useAlertsHistory({
http,
featureIds: [AlertConsumers.APM],
ruleId,
dateRange: { from: start, to: end },
queries: [
{
term: {
'kibana.alert.group.value': {
value: 'host=1',
},
},
},
],
}),
{
wrapper,
}
);
await act(async () => {
await waitFor(() => result.current.isSuccess);
});
expect(mockedHttpPost).toBeCalledWith('/internal/rac/alerts/find', {
body:
'{"size":0,"feature_ids":["apm"],"query":{"bool":{"must":[' +
'{"term":{"kibana.alert.rule.uuid":"cfd36e60-ef22-11ed-91eb-b7893acacfe2"}},' +
'{"term":{"kibana.alert.group.value":{"value":"host=1"}}},' +
'{"range":{"kibana.alert.time_range":{"from":"2023-04-10T00:00:00.000Z","to":"2023-05-10T00:00:00.000Z"}}}]}},' +
'"aggs":{"histogramTriggeredAlerts":{"date_histogram":{"field":"kibana.alert.start","fixed_interval":"1d",' +
'"extended_bounds":{"min":"2023-04-10T00:00:00.000Z","max":"2023-05-10T00:00:00.000Z"}}},' +
'"avgTimeToRecoverUS":{"filter":{"term":{"kibana.alert.status":"recovered"}},' +
'"aggs":{"recoveryTime":{"avg":{"field":"kibana.alert.duration.us"}}}}}}',
signal,
});
});
});

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { AggregationsDateHistogramBucketKeys } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { type HttpSetup } from '@kbn/core/public';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { AggregationsDateHistogramBucketKeys } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
ALERT_DURATION,
ALERT_RULE_UUID,
@ -26,6 +27,7 @@ export interface Props {
from: string;
to: string;
};
queries?: QueryDslQueryContainer[];
}
interface FetchAlertsHistory {
@ -45,7 +47,13 @@ export const EMPTY_ALERTS_HISTORY = {
histogramTriggeredAlerts: [] as AggregationsDateHistogramBucketKeys[],
avgTimeToRecoverUS: 0,
};
export function useAlertsHistory({ featureIds, ruleId, dateRange, http }: Props): UseAlertsHistory {
export function useAlertsHistory({
featureIds,
ruleId,
dateRange,
http,
queries,
}: Props): UseAlertsHistory {
const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({
queryKey: ['useAlertsHistory'],
queryFn: async ({ signal }) => {
@ -58,6 +66,7 @@ export function useAlertsHistory({ featureIds, ruleId, dateRange, http }: Props)
ruleId,
dateRange,
signal,
queries,
});
},
refetchOnWindowFocus: false,
@ -87,12 +96,14 @@ interface AggsESResponse {
};
};
}
export async function fetchTriggeredAlertsHistory({
featureIds,
http,
ruleId,
dateRange,
signal,
queries = [],
}: {
featureIds: ValidFeatureId[];
http: HttpSetup;
@ -102,6 +113,7 @@ export async function fetchTriggeredAlertsHistory({
to: string;
};
signal?: AbortSignal;
queries?: QueryDslQueryContainer[];
}): Promise<FetchAlertsHistory> {
try {
const responseES = await http.post<AggsESResponse>(`${BASE_RAC_ALERTS_API_PATH}/find`, {
@ -117,6 +129,7 @@ export async function fetchTriggeredAlertsHistory({
[ALERT_RULE_UUID]: ruleId,
},
},
...queries,
{
range: {
[ALERT_TIME_RANGE]: dateRange,

View file

@ -3,6 +3,18 @@
exports[`AlertDetailsAppSection should render annotations 1`] = `
Array [
Object {
"additionalFilters": Array [
Object {
"meta": Object {},
"query": Object {
"term": Object {
"host.name": Object {
"value": "host-1",
},
},
},
},
],
"annotations": Array [
Object {
"color": "#BD271E",
@ -27,6 +39,9 @@ Array [
"type": "manual",
},
],
"chartOptions": Object {
"seriesType": "bar_stacked",
},
"dataView": undefined,
"groupBy": Array [
"host.hostname",
@ -52,7 +67,6 @@ Array [
"query": "host.hostname: Users-System.local and service.type: system",
},
},
"seriesType": "bar_stacked",
"timeRange": Object {
"from": "2023-03-28T10:43:13.802Z",
"to": "2023-03-29T13:14:09.581Z",

View file

@ -19,7 +19,8 @@ import {
} from '../../mocks/custom_threshold_rule';
import { CustomThresholdAlertFields } from '../../types';
import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart';
import AlertDetailsAppSection, { CustomThresholdAlert } from './alert_details_app_section';
import { CustomThresholdAlert } from '../types';
import AlertDetailsAppSection from './alert_details_app_section';
import { Groups } from './groups';
import { Tags } from './tags';
@ -28,6 +29,17 @@ const mockedChartStartContract = chartPluginMock.createStartContract();
jest.mock('@kbn/observability-alert-details', () => ({
AlertAnnotation: () => {},
AlertActiveTimeRangeAnnotation: () => {},
useAlertsHistory: () => ({
data: {
histogramTriggeredAlerts: [
{ key_as_string: '2023-04-10T00:00:00.000Z', key: 1681084800000, doc_count: 2 },
],
avgTimeToRecoverUS: 0,
totalTriggeredAlerts: 2,
},
isLoading: false,
isError: false,
}),
}));
jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import chroma from 'chroma-js';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useEffect, useState } from 'react';
@ -20,7 +21,7 @@ import {
useEuiTheme,
transparentize,
} from '@elastic/eui';
import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common';
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import {
ALERT_END,
@ -30,34 +31,27 @@ import {
TAGS,
} from '@kbn/rule-data-utils';
import { DataView } from '@kbn/data-views-plugin/common';
import chroma from 'chroma-js';
import type {
EventAnnotationConfig,
PointInTimeEventAnnotationConfig,
RangeEventAnnotationConfig,
} from '@kbn/event-annotation-common';
import moment from 'moment';
import { AlertHistoryChart } from './alert_history';
import { useLicense } from '../../../../hooks/use_license';
import { useKibana } from '../../../../utils/kibana_react';
import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter';
import { AlertSummaryField, TopAlert } from '../../../..';
import {
AlertParams,
CustomThresholdAlertFields,
CustomThresholdRuleTypeParams,
MetricExpression,
} from '../../types';
import { AlertSummaryField } from '../../../..';
import { AlertParams, MetricExpression } from '../../types';
import { TIME_LABELS } from '../criterion_preview_chart/criterion_preview_chart';
import { Threshold } from '../custom_threshold';
import { getGroupFilters } from '../helpers/get_group';
import { CustomThresholdRule, CustomThresholdAlert } from '../types';
import { LogRateAnalysis } from './log_rate_analysis';
import { Groups } from './groups';
import { Tags } from './tags';
import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart';
// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
export type CustomThresholdRule = Rule<CustomThresholdRuleTypeParams>;
export type CustomThresholdAlert = TopAlert<CustomThresholdAlertFields>;
interface AppSectionProps {
alert: CustomThresholdAlert;
rule: CustomThresholdRule;
@ -261,14 +255,18 @@ export default function AlertDetailsAppSection({
</EuiFlexItem>
<EuiFlexItem grow={5}>
<RuleConditionChart
metricExpression={criterion}
dataView={dataView}
searchConfiguration={ruleParams.searchConfiguration}
groupBy={ruleParams.groupBy}
additionalFilters={getGroupFilters(groups)}
annotations={annotations}
chartOptions={{
// For alert details page, the series type needs to be changed to 'bar_stacked'
// due to https://github.com/elastic/elastic-charts/issues/2323
seriesType: 'bar_stacked',
}}
dataView={dataView}
groupBy={ruleParams.groupBy}
metricExpression={criterion}
searchConfiguration={ruleParams.searchConfiguration}
timeRange={timeRange}
// For alert details page, the series type needs to be changed to 'bar_stacked' due to https://github.com/elastic/elastic-charts/issues/2323
seriesType={'bar_stacked'}
/>
</EuiFlexItem>
</EuiFlexGroup>
@ -278,6 +276,7 @@ export default function AlertDetailsAppSection({
{hasLogRateAnalysisLicense && (
<LogRateAnalysis alert={alert} dataView={dataView} rule={rule} services={services} />
)}
<AlertHistoryChart alert={alert} dataView={dataView} rule={rule} />
</EuiFlexGroup>
) : null;

View file

@ -0,0 +1,198 @@
/*
* 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 moment from 'moment';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { EventAnnotationConfig } from '@kbn/event-annotation-common';
import { DataView } from '@kbn/data-views-plugin/common';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiSpacer,
EuiLoadingSpinner,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALERT_GROUP, ALERT_GROUP_VALUE, type AlertConsumers } from '@kbn/rule-data-utils';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import { convertTo } from '../../../../../common/utils/formatters';
import { useKibana } from '../../../../utils/kibana_react';
import { AlertParams } from '../../types';
import { getGroupFilters, getGroupQueries } from '../helpers/get_group';
import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart';
import { CustomThresholdAlert, CustomThresholdRule } from '../types';
const DEFAULT_INTERVAL = '1d';
const SERIES_TYPE = 'bar_stacked';
interface Props {
alert: CustomThresholdAlert;
rule: CustomThresholdRule;
dataView?: DataView;
}
const dateRange = {
from: 'now-30d',
to: 'now',
};
export function AlertHistoryChart({ rule, dataView, alert }: Props) {
const { http, notifications } = useKibana().services;
const { euiTheme } = useEuiTheme();
const ruleParams = rule.params as RuleTypeParams & AlertParams;
const criterion = rule.params.criteria[0];
const groups = alert.fields[ALERT_GROUP];
const featureIds = [rule.consumer as AlertConsumers];
const {
data: { histogramTriggeredAlerts, avgTimeToRecoverUS, totalTriggeredAlerts },
isLoading,
isError,
} = useAlertsHistory({
http,
featureIds,
ruleId: rule.id,
dateRange,
queries: getGroupQueries(groups, ALERT_GROUP_VALUE),
});
// Only show alert history chart if there is only one condition
if (rule.params.criteria.length > 1) {
return null;
}
if (isError) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.observability.customThreshold.alertHistory.error.toastTitle', {
defaultMessage: 'Alerts history chart error',
}),
text: i18n.translate(
'xpack.observability.customThreshold.alertHistory.error.toastDescription',
{
defaultMessage: `An error occurred when fetching alert history chart data`,
}
),
});
}
const annotations: EventAnnotationConfig[] =
histogramTriggeredAlerts
?.filter((annotation) => annotation.doc_count > 0)
.map((annotation) => {
return {
type: 'manual',
id: uuidv4(),
label: String(annotation.doc_count),
key: {
type: 'point_in_time',
timestamp: moment(new Date(annotation.key_as_string!)).toISOString(),
},
lineWidth: 2,
color: euiTheme.colors.danger,
icon: 'alert',
textVisibility: true,
};
}) || [];
return (
<EuiPanel hasBorder={true} data-test-subj="AlertDetails">
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.observability.customThreshold.alertHistory.chartTitle', {
defaultMessage: 'Alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.observability.customThreshold.alertHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>
{isLoading ? <EuiLoadingSpinner size="s" /> : totalTriggeredAlerts || '-'}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.observability.customThreshold.alertHistory.alertsTriggered',
{
defaultMessage: 'Alerts triggered',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.observability.customThreshold.alertHistory.avgTimeToRecover', {
defaultMessage: 'Avg time to recover',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<RuleConditionChart
additionalFilters={getGroupFilters(groups)}
annotations={annotations}
chartOptions={{
// For alert details page, the series type needs to be changed to 'bar_stacked'
// due to https://github.com/elastic/elastic-charts/issues/2323
seriesType: SERIES_TYPE,
interval: DEFAULT_INTERVAL,
}}
dataView={dataView}
groupBy={ruleParams.groupBy}
metricExpression={criterion}
searchConfiguration={ruleParams.searchConfiguration}
timeRange={dateRange}
/>
</EuiPanel>
);
}

View file

@ -1,39 +0,0 @@
/*
* 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 { getFilterQuery } from './get_filter_query';
describe('getFilterQuery', () => {
it('should generate correct filter query when original query is not empty', () => {
const query = 'container.id: container-1';
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
expect(getFilterQuery(query, groups)).toBe(
'(container.id: container-1) and container.id: container-0 and host.name: host-0'
);
});
it('should generate correct filter query when original query is empty', () => {
const query = '';
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
expect(getFilterQuery(query, groups)).toBe('container.id: container-0 and host.name: host-0');
});
it('should generate correct filter query when original query and groups both are empty', () => {
const query = '';
const groups = undefined;
expect(getFilterQuery(query, groups)).toBe('');
});
});

View file

@ -1,21 +0,0 @@
/*
* 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.
*/
export const getFilterQuery = (
filter: string,
groups?: Array<{
field: string;
value: string;
}>
) => {
let query = filter;
if (groups) {
const groupQueries = groups?.map(({ field, value }) => `${field}: ${value}`).join(' and ');
query = query ? `(${query}) and ${groupQueries}` : groupQueries;
}
return query;
};

View file

@ -0,0 +1,88 @@
/*
* 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 { getGroupQueries, getGroupFilters } from './get_group';
describe('getGroup', () => {
describe('getGroupQueries', () => {
it('should generate correct query with default field name', () => {
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
expect(getGroupQueries(groups)).toEqual([
{ term: { 'container.id': { value: 'container-0' } } },
{ term: { 'host.name': { value: 'host-0' } } },
]);
});
it('should generate correct query with custom field name', () => {
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
const fieldName = 'custom.field';
expect(getGroupQueries(groups, fieldName)).toEqual([
{ term: { 'custom.field': { value: 'container-0' } } },
{ term: { 'custom.field': { value: 'host-0' } } },
]);
});
it('should return empty array when groups is empty', () => {
const groups = undefined;
expect(getGroupQueries(groups)).toEqual([]);
});
});
describe('getGroupFilters', () => {
it('should generate correct filter with default field name', () => {
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
expect(getGroupFilters(groups)).toEqual([
{
meta: {},
query: { term: { 'container.id': { value: 'container-0' } } },
},
{
meta: {},
query: { term: { 'host.name': { value: 'host-0' } } },
},
]);
});
it('should generate correct filter with custom field name', () => {
const groups = [
{ field: 'container.id', value: 'container-0' },
{ field: 'host.name', value: 'host-0' },
];
const fieldName = 'custom.field';
expect(getGroupFilters(groups, fieldName)).toEqual([
{
meta: {},
query: { term: { 'custom.field': { value: 'container-0' } } },
},
{
meta: {},
query: { term: { 'custom.field': { value: 'host-0' } } },
},
]);
});
it('should return empty array when groups is empty', () => {
const groups = undefined;
expect(getGroupFilters(groups)).toEqual([]);
});
});
});

View file

@ -0,0 +1,37 @@
/*
* 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 { Filter } from '@kbn/es-query';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { Group } from '../types';
/*
* groupFieldName
* In some cases, like AAD indices, the field name for group value is differen from group.field,
* in AAD case, it is ALERT_GROUP_VALUE (`kibana.alert.group.value`). groupFieldName allows
* passing a different field name to be used in the query.
*/
export const getGroupQueries = (
groups?: Group[],
groupFieldName?: string
): QueryDslQueryContainer[] => {
return (
(groups &&
groups.map((group) => ({
term: {
[groupFieldName || group.field]: {
value: group.value,
},
},
}))) ||
[]
);
};
export const getGroupFilters = (groups?: Group[], groupFieldName?: string): Filter[] => {
return getGroupQueries(groups, groupFieldName).map((query) => ({ meta: {}, query }));
};

View file

@ -8,7 +8,7 @@
import React, { useState, useEffect } from 'react';
import { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import { EuiEmptyPrompt, useEuiTheme } from '@elastic/eui';
import { Query } from '@kbn/es-query';
import { Query, Filter } from '@kbn/es-query';
import { FillStyle, SeriesType } from '@kbn/lens-plugin/public';
import { DataView } from '@kbn/data-views-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
@ -39,6 +39,11 @@ import {
LensFieldFormat,
} from './helpers';
interface ChartOptions {
seriesType?: SeriesType;
interval?: string;
}
interface RuleConditionChartProps {
metricExpression: MetricExpression;
searchConfiguration: SerializedSearchSourceFields;
@ -47,7 +52,8 @@ interface RuleConditionChartProps {
error?: IErrorObject;
timeRange: TimeRange;
annotations?: EventAnnotationConfig[];
seriesType?: SeriesType;
chartOptions?: ChartOptions;
additionalFilters?: Filter[];
}
const defaultQuery: Query = {
@ -63,7 +69,8 @@ export function RuleConditionChart({
error,
annotations,
timeRange,
seriesType,
chartOptions: { seriesType, interval } = {},
additionalFilters = [],
}: RuleConditionChartProps) {
const {
services: { lens },
@ -76,6 +83,7 @@ export function RuleConditionChart({
const [thresholdReferenceLine, setThresholdReferenceLine] = useState<XYReferenceLinesLayer[]>();
const [alertAnnotation, setAlertAnnotation] = useState<XYByValueAnnotationsLayer>();
const [chartLoading, setChartLoading] = useState<boolean>(false);
const filters = [...(searchConfiguration.filter || []), ...additionalFilters];
const formulaAsync = useAsync(() => {
return lens.stateHelperApi();
}, [lens]);
@ -231,7 +239,7 @@ export function RuleConditionChart({
buckets: {
type: 'date_histogram',
params: {
interval: `${timeSize}${timeUnit}`,
interval: interval || `${timeSize}${timeUnit}`,
},
},
seriesType: seriesType ? seriesType : 'bar',
@ -295,6 +303,7 @@ export function RuleConditionChart({
formula,
formulaAsync.value,
groupBy,
interval,
metrics,
threshold,
thresholdReferenceLine,
@ -336,6 +345,7 @@ export function RuleConditionChart({
</div>
);
}
return (
<div>
<lens.EmbeddableComponent
@ -346,7 +356,7 @@ export function RuleConditionChart({
attributes={attributes}
disableTriggers={true}
query={(searchConfiguration.query as Query) || defaultQuery}
filters={searchConfiguration.filter}
filters={filters}
/>
</div>
);

View file

@ -0,0 +1,19 @@
/*
* 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 { Rule } from '@kbn/alerting-plugin/common';
import { TopAlert } from '../../..';
import { CustomThresholdAlertFields, CustomThresholdRuleTypeParams } from '../types';
// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
export type CustomThresholdRule = Rule<CustomThresholdRuleTypeParams>;
export type CustomThresholdAlert = TopAlert<CustomThresholdAlertFields>;
export interface Group {
field: string;
value: string;
}

View file

@ -10,10 +10,7 @@ import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
import { CustomThresholdAlertFields } from '../types';
import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types';
import {
CustomThresholdAlert,
CustomThresholdRule,
} from '../components/alert_details_app_section/alert_details_app_section';
import { CustomThresholdAlert, CustomThresholdRule } from '../components/types';
export const buildCustomThresholdRule = (
rule: Partial<CustomThresholdRule> = {}