Skip to content

Commit

Permalink
[OBX-UI-MNGMT] Align the Metric rule charts by using Lens in Alert de…
Browse files Browse the repository at this point in the history
…tails page and Creation Rule flyout (elastic#184950)

## Summary
Fixes elastic#184922
Fixes elastic#184574

It uses the `RuleConditionChart`, a.k.a Lens chart, for the Metric
Threshold rule.
### Implemented in both places:
- Metric Alert Details page
![Screenshot 2024-06-10 at 16 12
43](https://github.com/elastic/kibana/assets/6838659/9d88d9b9-fe5d-4f8d-9e5a-538c52c58692)

- Rule creation flyout
![Screenshot 2024-06-10 at 16 13
18](https://github.com/elastic/kibana/assets/6838659/8c9ca3b3-2fbf-4cfa-83c9-00278c5e8e77)
  • Loading branch information
fkanout authored Jun 20, 2024
1 parent 1e1e35b commit 85f1280
Show file tree
Hide file tree
Showing 20 changed files with 354 additions and 135 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,53 @@ import {
buildMetricThresholdRule,
} from '../mocks/metric_threshold_rule';
import { AlertDetailsAppSection } from './alert_details_app_section';
import { ExpressionChart } from './expression_chart';
import { RuleConditionChart } from '@kbn/observability-plugin/public';
import { lensPluginMock } from '@kbn/lens-plugin/public/mocks';

const mockedChartStartContract = chartPluginMock.createStartContract();
const mockedLensStartContract = lensPluginMock.createStartContract();

Date.now = jest.fn(() => new Date('2024-06-13T07:00:33.381Z').getTime());

jest.mock('../../../containers/metrics_source', () => ({
useMetricsDataViewContext: () => ({
metricsView: { dataViewReference: 'index' },
}),
withSourceProvider:
<ComponentProps extends {}>(Component: React.FC<ComponentProps>) =>
() => {
return function ComponentWithSourceProvider(props: ComponentProps) {
return <div />;
};
},
}));

jest.mock('@kbn/observability-alert-details', () => ({
AlertAnnotation: () => {},
AlertActiveTimeRangeAnnotation: () => {},
}));

jest.mock('@kbn/observability-alert-details', () => ({
AlertAnnotation: () => {},
AlertActiveTimeRangeAnnotation: () => {},
}));
jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({
getPaddedAlertTimeRange: () => ({
from: '2023-03-28T10:43:13.802Z',
to: '2023-03-29T13:14:09.581Z',
}),
}));

jest.mock('./expression_chart', () => ({
ExpressionChart: jest.fn(() => <div data-test-subj="ExpressionChart" />),
jest.mock('@kbn/observability-plugin/public', () => ({
RuleConditionChart: jest.fn(() => <div data-test-subj="RuleConditionChart" />),
getGroupFilters: jest.fn(),
}));

jest.mock('../../../hooks/use_kibana', () => ({
useKibanaContextForPlugin: () => ({
services: {
...mockCoreMock.createStart(),
charts: mockedChartStartContract,
lens: mockedLensStartContract,
},
}),
}));
Expand Down Expand Up @@ -74,11 +96,11 @@ describe('AlertDetailsAppSection', () => {
});

it('should render annotations', async () => {
const mockedExpressionChart = jest.fn(() => <div data-test-subj="ExpressionChart" />);
(ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart);
const mockedRuleConditionChart = jest.fn(() => <div data-test-subj="RuleConditionChart" />);
(RuleConditionChart as jest.Mock).mockImplementation(mockedRuleConditionChart);
renderComponent();

expect(mockedExpressionChart).toHaveBeenCalledTimes(3);
expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot();
expect(mockedRuleConditionChart).toHaveBeenCalledTimes(3);
expect(mockedRuleConditionChart.mock.calls[0]).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@ import {
EuiPanel,
EuiSpacer,
EuiTitle,
transparentize,
useEuiTheme,
} from '@elastic/eui';
import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public';
import chroma from 'chroma-js';

import { AlertSummaryField, RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public';
import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils';
import { Rule } from '@kbn/alerting-plugin/common';
import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details';
import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import type {
EventAnnotationConfig,
PointInTimeEventAnnotationConfig,
RangeEventAnnotationConfig,
} from '@kbn/event-annotation-common';

import { getGroupFilters } from '@kbn/observability-plugin/public';
import type { GenericAggType } from '@kbn/observability-plugin/public';
import { metricValueFormatter } from '../../../../common/alerting/metrics/metric_value_formatter';
import { Threshold } from '../../common/components/threshold';
import { withSourceProvider } from '../../../containers/metrics_source';
import { useMetricsDataViewContext, withSourceProvider } from '../../../containers/metrics_source';
import { generateUniqueKey } from '../lib/generate_unique_key';
import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { MetricThresholdRuleTypeParams } from '..';
import { ExpressionChart } from './expression_chart';
import { AlertParams } from '../types';

// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
export type MetricThresholdRule = Rule<
MetricThresholdRuleTypeParams & {
filterQueryText?: string;
groupBy?: string | string[];
}
>;
export type MetricThresholdRule = Rule<RuleTypeParams & AlertParams>;

interface Group {
field: string;
Expand All @@ -51,41 +54,49 @@ interface MetricThresholdAlertField {

export type MetricThresholdAlert = TopAlert<MetricThresholdAlertField>;

const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm';
const ALERT_START_ANNOTATION_ID = 'alert_start_annotation';
const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation';

interface AppSectionProps {
alert: MetricThresholdAlert;
rule: MetricThresholdRule;
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
}

export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
const { uiSettings, charts } = useKibanaContextForPlugin().services;
export function AlertDetailsAppSection({ alert, rule, setAlertSummaryFields }: AppSectionProps) {
const { charts } = useKibanaContextForPlugin().services;
const { euiTheme } = useEuiTheme();
const groupInstance = alert.fields[ALERT_GROUP]?.map((group: Group) => group.value);

const groups = alert.fields[ALERT_GROUP];
const { metricsView } = useMetricsDataViewContext();
const chartProps = {
baseTheme: charts.theme.useChartsBaseTheme(),
};
const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined;
const annotations = [
<AlertAnnotation
alertStart={alert.start}
color={euiTheme.colors.danger}
dateFormat={uiSettings.get('dateFormat') || DEFAULT_DATE_FORMAT}
id={ALERT_START_ANNOTATION_ID}
key={ALERT_START_ANNOTATION_ID}
/>,
<AlertActiveTimeRangeAnnotation
alertStart={alert.start}
alertEnd={alertEnd}
color={euiTheme.colors.danger}
id={ALERT_TIME_RANGE_ANNOTATION_ID}
key={ALERT_TIME_RANGE_ANNOTATION_ID}
/>,
];
const alertEnd = alert.fields[ALERT_END];
const alertStart = alert.fields[ALERT_START];

const alertStartAnnotation: PointInTimeEventAnnotationConfig = {
label: 'Alert',
type: 'manual',
key: {
type: 'point_in_time',
timestamp: alertStart!,
},
color: euiTheme.colors.danger,
icon: 'alert',
id: 'metric_threshold_alert_start_annotation',
};

const alertRangeAnnotation: RangeEventAnnotationConfig = {
label: `${alertEnd ? 'Alert duration' : 'Active alert'}`,
type: 'manual',
key: {
type: 'range',
timestamp: alertStart!,
endTimestamp: alertEnd ?? moment().toISOString(),
},
color: chroma(transparentize('#F04E981A', 0.2)).hex().toUpperCase(),
id: `metric_threshold_${alertEnd ? 'recovered' : 'active'}_alert_range_annotation`,
};

const annotations: EventAnnotationConfig[] = [];
annotations.push(alertStartAnnotation, alertRangeAnnotation);

return !!rule.params.criteria ? (
<EuiFlexGroup direction="column" data-test-subj="metricThresholdAppSection">
Expand All @@ -94,10 +105,25 @@ export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
alert.fields[ALERT_START]!,
alert.fields[ALERT_END],
{
size: criterion.timeSize,
unit: criterion.timeUnit,
size: criterion.timeSize!,
unit: criterion.timeUnit!,
}
);
let metricExpression = [
{
aggType: criterion.aggType as GenericAggType,
name: String.fromCharCode('A'.charCodeAt(0) + index),
field: criterion.metric || '',
},
];
if (criterion.customMetrics) {
metricExpression = criterion.customMetrics.map((metric) => ({
name: metric.name,
aggType: metric.aggType as GenericAggType,
field: metric.field || '',
filter: metric.filter,
}));
}
return (
<EuiFlexItem key={generateUniqueKey(criterion)}>
<EuiPanel hasBorder hasShadow={false}>
Expand Down Expand Up @@ -135,16 +161,30 @@ export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
/>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<ExpressionChart
annotations={annotations}
chartType={MetricsExplorerChartType.line}
expression={criterion}
filterQuery={rule.params.filterQueryText}
groupBy={rule.params.groupBy}
groupInstance={groupInstance}
hideTitle
timeRange={timeRange}
/>
{metricsView && (
<RuleConditionChart
additionalFilters={getGroupFilters(groups)}
metricExpression={{
metrics: metricExpression,
threshold: criterion.threshold,
comparator: criterion.comparator,
timeSize: criterion.timeSize,
timeUnit: criterion.timeUnit,
warningComparator: criterion.warningComparator,
warningThreshold: criterion.warningThreshold,
}}
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',
}}
searchConfiguration={{ query: { query: '', language: '' } }}
timeRange={timeRange}
dataView={metricsView.dataViewReference}
groupBy={rule.params.groupBy}
annotations={annotations}
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
Expand Down
Loading

0 comments on commit 85f1280

Please sign in to comment.