mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[AO] Metric threshold alert details - custom time range and alert start annotation (#153954)
Closes #153202, closes #153850 ## Summary This PR adds alert start annotation and also uses a custom time range for the alert details' charts depending on the alert duration. The logic to calculate the time range was added in a separate package to be used in other use cases as well.  ## 🧪 How to test Create a metric threshold alert and go to the related alert details page, verify: - Alert start annotation - The time range of the charts should be before the alert was started (1/8 of the duration was added to each side) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ee72588faa
commit
6350e146fa
24 changed files with 418 additions and 30 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -471,6 +471,7 @@ src/plugins/newsfeed @elastic/kibana-core
|
|||
test/common/plugins/newsfeed @elastic/kibana-core
|
||||
x-pack/plugins/notifications @elastic/appex-sharedux
|
||||
packages/kbn-object-versioning @elastic/appex-sharedux
|
||||
x-pack/packages/observability/alert_details @elastic/actionable-observability
|
||||
x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops
|
||||
x-pack/plugins/observability @elastic/actionable-observability
|
||||
x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"controls": "src/plugins/controls",
|
||||
"data": "src/plugins/data",
|
||||
"ecsDataQualityDashboard": "x-pack/packages/kbn-ecs-data-quality-dashboard",
|
||||
"observabilityAlertDetails": "x-pack/packages/observability/alert_details",
|
||||
"dataViews": "src/plugins/data_views",
|
||||
"devTools": "src/plugins/dev_tools",
|
||||
"discover": "src/plugins/discover",
|
||||
|
|
|
@ -486,6 +486,7 @@
|
|||
"@kbn/newsfeed-test-plugin": "link:test/common/plugins/newsfeed",
|
||||
"@kbn/notifications-plugin": "link:x-pack/plugins/notifications",
|
||||
"@kbn/object-versioning": "link:packages/kbn-object-versioning",
|
||||
"@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details",
|
||||
"@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability",
|
||||
"@kbn/observability-plugin": "link:x-pack/plugins/observability",
|
||||
"@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider",
|
||||
|
|
|
@ -936,6 +936,8 @@
|
|||
"@kbn/notifications-plugin/*": ["x-pack/plugins/notifications/*"],
|
||||
"@kbn/object-versioning": ["packages/kbn-object-versioning"],
|
||||
"@kbn/object-versioning/*": ["packages/kbn-object-versioning/*"],
|
||||
"@kbn/observability-alert-details": ["x-pack/packages/observability/alert_details"],
|
||||
"@kbn/observability-alert-details/*": ["x-pack/packages/observability/alert_details/*"],
|
||||
"@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"],
|
||||
"@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"],
|
||||
"@kbn/observability-plugin": ["x-pack/plugins/observability"],
|
||||
|
|
3
x-pack/packages/observability/alert_details/README.md
Normal file
3
x-pack/packages/observability/alert_details/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/observability-alert-details
|
||||
|
||||
Provides alert details related helpers and components
|
9
x-pack/packages/observability/alert_details/index.ts
Normal file
9
x-pack/packages/observability/alert_details/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { AlertAnnotation } from './src/components/alert_annotation';
|
||||
export { getAlertTimeRange } from './src/helpers/get_alert_time_range';
|
12
x-pack/packages/observability/alert_details/jest.config.js
Normal file
12
x-pack/packages/observability/alert_details/jest.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/x-pack/packages/observability/alert_details'],
|
||||
};
|
5
x-pack/packages/observability/alert_details/kibana.jsonc
Normal file
5
x-pack/packages/observability/alert_details/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/observability-alert-details",
|
||||
"owner": "@elastic/actionable-observability"
|
||||
}
|
8
x-pack/packages/observability/alert_details/package.json
Normal file
8
x-pack/packages/observability/alert_details/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@kbn/observability-alert-details",
|
||||
"descriptio": "Helper and components related to alert details",
|
||||
"author": "Actionable Observability",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
alertStart: number;
|
||||
color: string;
|
||||
dateFormat: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const ANNOTATION_TITLE = i18n.translate('observabilityAlertDetails.alertAnnotation.title', {
|
||||
defaultMessage: 'Alert started',
|
||||
});
|
||||
|
||||
export function AlertAnnotation({ alertStart, color, dateFormat, id }: Props) {
|
||||
return (
|
||||
<LineAnnotation
|
||||
id={id}
|
||||
domainType={AnnotationDomainType.XDomain}
|
||||
dataValues={[
|
||||
{
|
||||
dataValue: alertStart,
|
||||
header: moment(alertStart).format(dateFormat),
|
||||
details: ANNOTATION_TITLE,
|
||||
},
|
||||
]}
|
||||
style={{
|
||||
line: {
|
||||
strokeWidth: 3,
|
||||
stroke: color,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
marker={<EuiIcon type="warning" color={color} />}
|
||||
markerPosition={Position.Top}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { getAlertTimeRange } from './get_alert_time_range';
|
||||
|
||||
describe('getAlertTimeRange', () => {
|
||||
const mockedDate = '2023-03-28T09:22:32.660Z';
|
||||
const mockDate = jest
|
||||
.spyOn(global.Date, 'now')
|
||||
.mockImplementation(() => new Date(mockedDate).valueOf());
|
||||
|
||||
afterAll(() => mockDate.mockRestore());
|
||||
const testData: any[] = [
|
||||
// Description, Start, End, Output
|
||||
[
|
||||
'Duration 4 hour, time range will be extended it with 30 minutes from each side',
|
||||
'2023-03-28T04:15:32.660Z',
|
||||
'2023-03-28T08:15:32.660Z',
|
||||
{ from: '2023-03-28T03:45:32.660Z', to: '2023-03-28T08:45:32.660Z' },
|
||||
],
|
||||
[
|
||||
'Duration 5 minutes, time range will be extended it with 20 minutes from each side',
|
||||
'2023-03-28T08:22:33.660Z',
|
||||
'2023-03-28T08:27:33.660Z',
|
||||
{ from: '2023-03-28T08:02:33.660Z', to: '2023-03-28T08:47:33.660Z' },
|
||||
],
|
||||
];
|
||||
|
||||
it.each(testData)('%s', (_, start, end, output) => {
|
||||
expect(getAlertTimeRange(start, end)).toEqual(output);
|
||||
});
|
||||
|
||||
describe('active alert', () => {
|
||||
it('without end time', () => {
|
||||
// Duration 5 hours
|
||||
const start = '2023-03-28T04:22:32.660Z';
|
||||
const output = {
|
||||
// Time range is from 37.5 minutes (duration/8) before start
|
||||
from: '2023-03-28T03:45:02.660Z',
|
||||
to: mockedDate,
|
||||
};
|
||||
expect(getAlertTimeRange(start)).toEqual(output);
|
||||
});
|
||||
|
||||
it('with end time than 10 minutes before now', () => {
|
||||
const start = '2023-03-28T05:17:32.660Z';
|
||||
// 5 minutes before now, duration 4 hours
|
||||
const end = '2023-03-28T09:17:32.660Z';
|
||||
const output = {
|
||||
// Time range is from 30 minutes (duration/8) before start
|
||||
from: '2023-03-28T04:47:32.660Z',
|
||||
to: mockedDate,
|
||||
};
|
||||
expect(getAlertTimeRange(start, end)).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export interface TimeRange {
|
||||
from?: string;
|
||||
to?: string;
|
||||
interval?: string;
|
||||
}
|
||||
|
||||
export const getAlertTimeRange = (alertStart: string, alertEnd?: string): TimeRange => {
|
||||
const alertDuration = moment.duration(moment(alertEnd).diff(moment(alertStart)));
|
||||
const now = moment().toISOString();
|
||||
const durationMs =
|
||||
alertDuration.asMinutes() < 160
|
||||
? moment.duration(20, 'minutes').asMilliseconds()
|
||||
: alertDuration.asMilliseconds() / 8;
|
||||
|
||||
const from = moment(alertStart).subtract(durationMs, 'millisecond').toISOString();
|
||||
const to =
|
||||
alertEnd && moment(alertEnd).add(durationMs, 'millisecond').isBefore(now)
|
||||
? moment(alertEnd).add(durationMs, 'millisecond').toISOString()
|
||||
: now;
|
||||
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
};
|
||||
};
|
21
x-pack/packages/observability/alert_details/tsconfig.json
Normal file
21
x-pack/packages/observability/alert_details/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/i18n"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AlertDetailsAppSection should render annotation 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"annotations": Array [
|
||||
<AlertAnnotation
|
||||
alertStart={1678716383695}
|
||||
color="#BD271E"
|
||||
dateFormat="YYYY-MM-DD HH:mm"
|
||||
id="annotation_alert_start"
|
||||
/>,
|
||||
],
|
||||
"chartType": "line",
|
||||
"derivedIndexPattern": Object {
|
||||
"fields": Array [],
|
||||
"title": "metricbeat-*",
|
||||
},
|
||||
"expression": Object {
|
||||
"aggType": "count",
|
||||
"comparator": ">",
|
||||
"threshold": Array [
|
||||
2000,
|
||||
],
|
||||
"timeSize": 15,
|
||||
"timeUnit": "m",
|
||||
},
|
||||
"filterQuery": undefined,
|
||||
"groupBy": Array [
|
||||
"host.hostname",
|
||||
],
|
||||
"source": Object {
|
||||
"id": "default",
|
||||
},
|
||||
"timeRange": Object {
|
||||
"from": "2023-03-28T10:43:13.802Z",
|
||||
"to": "2023-03-29T13:14:09.581Z",
|
||||
},
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
`;
|
|
@ -5,13 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
||||
import React from 'react';
|
||||
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { buildMetricThresholdRule } from '../mocks/metric_threshold_rule';
|
||||
import {
|
||||
buildMetricThresholdAlert,
|
||||
buildMetricThresholdRule,
|
||||
} from '../mocks/metric_threshold_rule';
|
||||
import { AlertDetailsAppSection } from './alert_details_app_section';
|
||||
import { ExpressionChart } from './expression_chart';
|
||||
|
||||
jest.mock('@kbn/observability-alert-details', () => ({
|
||||
AlertAnnotation: () => {},
|
||||
getAlertTimeRange: () => ({ 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('../../../hooks/use_kibana', () => ({
|
||||
useKibanaContextForPlugin: () => ({
|
||||
|
@ -33,15 +46,27 @@ describe('AlertDetailsAppSection', () => {
|
|||
return render(
|
||||
<IntlProvider locale="en">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AlertDetailsAppSection rule={buildMetricThresholdRule()} />
|
||||
<AlertDetailsAppSection
|
||||
alert={buildMetricThresholdAlert()}
|
||||
rule={buildMetricThresholdRule()}
|
||||
/>
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('should render rule data correctly', async () => {
|
||||
it('should render rule data', async () => {
|
||||
const result = renderComponent();
|
||||
|
||||
expect((await result.findByTestId('metricThresholdAppSection')).children.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should render annotation', async () => {
|
||||
const mockedExpressionChart = jest.fn(() => <div data-test-subj="ExpressionChart" />);
|
||||
(ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart);
|
||||
renderComponent();
|
||||
|
||||
expect(mockedExpressionChart).toHaveBeenCalledTimes(3);
|
||||
expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,32 +5,55 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, useEuiTheme } from '@elastic/eui';
|
||||
import { TopAlert } from '@kbn/observability-plugin/public';
|
||||
import { ALERT_END, ALERT_START } from '@kbn/rule-data-utils';
|
||||
import { Rule } from '@kbn/alerting-plugin/common';
|
||||
import { AlertAnnotation, getAlertTimeRange } from '@kbn/observability-alert-details';
|
||||
import { useSourceContext, withSourceProvider } from '../../../containers/metrics_source';
|
||||
import { MetricThresholdRuleTypeParams } from '..';
|
||||
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';
|
||||
|
||||
// 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 MetricThresholdAlert = TopAlert;
|
||||
|
||||
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||
const ALERT_START_ANNOTATION_ID = 'annotation_alert_start';
|
||||
|
||||
interface AppSectionProps {
|
||||
rule: Rule<
|
||||
MetricThresholdRuleTypeParams & {
|
||||
filterQueryText?: string;
|
||||
groupBy?: string | string[];
|
||||
}
|
||||
>;
|
||||
rule: MetricThresholdRule;
|
||||
alert: MetricThresholdAlert;
|
||||
}
|
||||
|
||||
export function AlertDetailsAppSection({ rule }: AppSectionProps) {
|
||||
export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
|
||||
const { uiSettings } = useKibanaContextForPlugin().services;
|
||||
const { source, createDerivedIndexPattern } = useSourceContext();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const derivedIndexPattern = useMemo(
|
||||
() => createDerivedIndexPattern(),
|
||||
[createDerivedIndexPattern]
|
||||
);
|
||||
const timeRange = getAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]);
|
||||
const annotations = [
|
||||
<AlertAnnotation
|
||||
key={ALERT_START_ANNOTATION_ID}
|
||||
alertStart={alert.start}
|
||||
color={euiTheme.colors.danger}
|
||||
dateFormat={uiSettings.get('dateFormat') || DEFAULT_DATE_FORMAT}
|
||||
id={ALERT_START_ANNOTATION_ID}
|
||||
/>,
|
||||
];
|
||||
|
||||
return !!rule.params.criteria ? (
|
||||
<EuiFlexGroup direction="column" data-test-subj="metricThresholdAppSection">
|
||||
|
@ -44,6 +67,8 @@ export function AlertDetailsAppSection({ rule }: AppSectionProps) {
|
|||
filterQuery={rule.params.filterQueryText}
|
||||
groupBy={rule.params.groupBy}
|
||||
chartType={MetricsExplorerChartType.line}
|
||||
timeRange={timeRange}
|
||||
annotations={annotations}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { LineAnnotation, RectAnnotation } from '@elastic/charts';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock`
|
||||
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
||||
import { Aggregators, Comparator } from '../../../../common/alerting/metrics';
|
||||
|
@ -38,7 +39,12 @@ jest.mock('../hooks/use_metrics_explorer_chart_data', () => ({
|
|||
}));
|
||||
|
||||
describe('ExpressionChart', () => {
|
||||
async function setup(expression: MetricExpression, filterQuery?: string, groupBy?: string) {
|
||||
async function setup(
|
||||
expression: MetricExpression,
|
||||
filterQuery?: string,
|
||||
groupBy?: string,
|
||||
annotations?: Array<ReactElement<typeof RectAnnotation | typeof LineAnnotation>>
|
||||
) {
|
||||
const derivedIndexPattern: DataViewBase = {
|
||||
title: 'metricbeat-*',
|
||||
fields: [],
|
||||
|
@ -64,6 +70,7 @@ describe('ExpressionChart', () => {
|
|||
source={source}
|
||||
filterQuery={filterQuery}
|
||||
groupBy={groupBy}
|
||||
annotations={annotations}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -5,8 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Axis, Chart, niceTimeFormatter, Position, Settings } from '@elastic/charts';
|
||||
import React, { ReactElement } from 'react';
|
||||
import {
|
||||
Axis,
|
||||
Chart,
|
||||
LineAnnotation,
|
||||
niceTimeFormatter,
|
||||
Position,
|
||||
RectAnnotation,
|
||||
Settings,
|
||||
} from '@elastic/charts';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
|
@ -16,7 +24,7 @@ import { MetricsSourceConfiguration } from '../../../../common/metrics_sources';
|
|||
import { Color } from '../../../../common/color_palette';
|
||||
import { MetricsExplorerRow, MetricsExplorerAggregation } from '../../../../common/http_api';
|
||||
import { MetricExplorerSeriesChart } from '../../../pages/metrics/metrics_explorer/components/series_chart';
|
||||
import { MetricExpression } from '../types';
|
||||
import { MetricExpression, TimeRange } from '../types';
|
||||
import {
|
||||
MetricsExplorerChartType,
|
||||
MetricsExplorerOptionsMetric,
|
||||
|
@ -44,6 +52,8 @@ interface Props {
|
|||
filterQuery?: string;
|
||||
groupBy?: string | string[];
|
||||
chartType?: MetricsExplorerChartType;
|
||||
timeRange?: TimeRange;
|
||||
annotations?: Array<ReactElement<typeof RectAnnotation | typeof LineAnnotation>>;
|
||||
}
|
||||
|
||||
export const ExpressionChart: React.FC<Props> = ({
|
||||
|
@ -53,6 +63,8 @@ export const ExpressionChart: React.FC<Props> = ({
|
|||
filterQuery,
|
||||
groupBy,
|
||||
chartType = MetricsExplorerChartType.bar,
|
||||
timeRange,
|
||||
annotations,
|
||||
}) => {
|
||||
const { uiSettings } = useKibanaContextForPlugin().services;
|
||||
|
||||
|
@ -61,7 +73,8 @@ export const ExpressionChart: React.FC<Props> = ({
|
|||
derivedIndexPattern,
|
||||
source,
|
||||
filterQuery,
|
||||
groupBy
|
||||
groupBy,
|
||||
timeRange
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
|
@ -158,6 +171,7 @@ export const ExpressionChart: React.FC<Props> = ({
|
|||
domain={domain}
|
||||
/>
|
||||
)}
|
||||
{annotations}
|
||||
<Axis
|
||||
id={'timestamp'}
|
||||
position={Position.Bottom}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { DataViewBase } from '@kbn/es-query';
|
|||
import { useMemo } from 'react';
|
||||
import { MetricExpressionCustomMetric } from '../../../../common/alerting/metrics';
|
||||
import { MetricsSourceConfiguration } from '../../../../common/metrics_sources';
|
||||
import { MetricExpression } from '../types';
|
||||
import { MetricExpression, TimeRange } from '../types';
|
||||
import {
|
||||
MetricsExplorerOptions,
|
||||
MetricsExplorerTimestampsRT,
|
||||
|
@ -18,12 +18,15 @@ import {
|
|||
import { useMetricsExplorerData } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data';
|
||||
import { MetricExplorerCustomMetricAggregations } from '../../../../common/http_api/metrics_explorer';
|
||||
|
||||
const DEFAULT_TIME_RANGE = {};
|
||||
|
||||
export const useMetricsExplorerChartData = (
|
||||
expression: MetricExpression,
|
||||
derivedIndexPattern: DataViewBase,
|
||||
source?: MetricsSourceConfiguration,
|
||||
filterQuery?: string,
|
||||
groupBy?: string | string[]
|
||||
groupBy?: string | string[],
|
||||
timeRange: TimeRange = DEFAULT_TIME_RANGE
|
||||
) => {
|
||||
const { timeSize, timeUnit } = expression || { timeSize: 1, timeUnit: 'm' };
|
||||
|
||||
|
@ -57,8 +60,8 @@ export const useMetricsExplorerChartData = (
|
|||
]
|
||||
);
|
||||
const timestamps: MetricsExplorerTimestampsRT = useMemo(() => {
|
||||
const from = `now-${(timeSize || 1) * 20}${timeUnit}`;
|
||||
const to = 'now';
|
||||
const from = timeRange.from ?? `now-${(timeSize || 1) * 20}${timeUnit}`;
|
||||
const to = timeRange.to ?? 'now';
|
||||
const fromTimestamp = DateMath.parse(from)!.valueOf();
|
||||
const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf();
|
||||
return {
|
||||
|
@ -66,7 +69,7 @@ export const useMetricsExplorerChartData = (
|
|||
fromTimestamp,
|
||||
toTimestamp,
|
||||
};
|
||||
}, [timeSize, timeUnit]);
|
||||
}, [timeRange, timeSize, timeUnit]);
|
||||
|
||||
return useMetricsExplorerData(options, source?.configuration, derivedIndexPattern, timestamps);
|
||||
};
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Rule } from '@kbn/alerting-plugin/common';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Aggregators, Comparator } from '../../../../common/alerting/metrics';
|
||||
import { MetricThresholdRuleTypeParams } from '..';
|
||||
import { MetricThresholdAlert, MetricThresholdRule } from '../components/alert_details_app_section';
|
||||
|
||||
export const buildMetricThresholdRule = (
|
||||
rule: Partial<Rule<MetricThresholdRuleTypeParams>> = {}
|
||||
): Rule<MetricThresholdRuleTypeParams> => {
|
||||
rule: Partial<MetricThresholdRule> = {}
|
||||
): MetricThresholdRule => {
|
||||
return {
|
||||
alertTypeId: 'metrics.alert.threshold',
|
||||
createdBy: 'admin',
|
||||
|
@ -85,7 +84,7 @@ export const buildMetricThresholdRule = (
|
|||
},
|
||||
],
|
||||
filterQuery:
|
||||
'{"bool":{"filter":[{"bool":{"should":[{"term":{"host.hostname":{"value":"Maryams-MacBook-Pro.local"}}}],"minimum_should_match":1}},{"bool":{"should":[{"term":{"service.type":{"value":"system"}}}],"minimum_should_match":1}}]}}',
|
||||
'{"bool":{"filter":[{"bool":{"should":[{"term":{"host.hostname":{"value":"Users-System.local"}}}],"minimum_should_match":1}},{"bool":{"should":[{"term":{"service.type":{"value":"system"}}}],"minimum_should_match":1}}]}}',
|
||||
groupBy: ['host.hostname'],
|
||||
},
|
||||
monitoring: {
|
||||
|
@ -120,3 +119,60 @@ export const buildMetricThresholdRule = (
|
|||
...rule,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildMetricThresholdAlert = (
|
||||
alert: Partial<MetricThresholdAlert> = {}
|
||||
): MetricThresholdAlert => {
|
||||
return {
|
||||
link: '/app/metrics/explorer',
|
||||
reason: 'system.cpu.user.pct reported no data in the last 1m for ',
|
||||
fields: {
|
||||
'kibana.alert.rule.parameters': {
|
||||
criteria: [
|
||||
{
|
||||
aggType: 'avg',
|
||||
comparator: '>',
|
||||
threshold: [0.1],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
metric: 'system.cpu.user.pct',
|
||||
},
|
||||
],
|
||||
sourceId: 'default',
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
},
|
||||
'kibana.alert.rule.category': 'Metric threshold',
|
||||
'kibana.alert.rule.consumer': 'alerts',
|
||||
'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7',
|
||||
'kibana.alert.rule.name': 'One condition',
|
||||
'kibana.alert.rule.producer': 'infrastructure',
|
||||
'kibana.alert.rule.rule_type_id': 'metrics.alert.threshold',
|
||||
'kibana.alert.rule.uuid': '3a1ed8c0-c1a8-11ed-9249-ed6d75986bdc',
|
||||
'kibana.space_ids': ['default'],
|
||||
'kibana.alert.rule.tags': [],
|
||||
'@timestamp': '2023-03-28T14:40:00.000Z',
|
||||
'kibana.alert.reason': 'system.cpu.user.pct reported no data in the last 1m for ',
|
||||
'kibana.alert.action_group': 'metrics.threshold.nodata',
|
||||
tags: [],
|
||||
'kibana.alert.duration.us': 248391946000,
|
||||
'kibana.alert.time_range': {
|
||||
gte: '2023-03-13T14:06:23.695Z',
|
||||
},
|
||||
'kibana.alert.instance.id': '*',
|
||||
'kibana.alert.start': '2023-03-28T13:40:00.000Z',
|
||||
'kibana.alert.uuid': '50faddcd-c0a0-4122-a068-c204f4a7ec87',
|
||||
'kibana.alert.status': 'active',
|
||||
'kibana.alert.workflow_status': 'open',
|
||||
'event.kind': 'signal',
|
||||
'event.action': 'active',
|
||||
'kibana.version': '8.8.0',
|
||||
'kibana.alert.flapping': false,
|
||||
'kibana.alert.rule.revision': 1,
|
||||
},
|
||||
active: true,
|
||||
start: 1678716383695,
|
||||
lastUpdated: 1678964775641,
|
||||
...alert,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -58,6 +58,11 @@ export interface ExpressionChartRow {
|
|||
|
||||
export type ExpressionChartSeries = ExpressionChartRow[][];
|
||||
|
||||
export interface TimeRange {
|
||||
from?: string;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export interface AlertParams {
|
||||
criteria: MetricExpression[];
|
||||
groupBy?: string | string[];
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"@kbn/shared-ux-prompt-not-found",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/shared-ux-link-redirect-app",
|
||||
"@kbn/observability-alert-details",
|
||||
"@kbn/actions-plugin",
|
||||
"@kbn/ui-theme"
|
||||
],
|
||||
|
|
Binary file not shown.
|
@ -4609,6 +4609,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/observability-alert-details@link:x-pack/packages/observability/alert_details":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/observability-fixtures-plugin@link:x-pack/test/cases_api_integration/common/plugins/observability":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue