From f0b93a7a4d063586e3206fe5ff0e2165b6649a89 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Thu, 26 Oct 2023 13:27:08 +0200 Subject: [PATCH 01/17] Rename metricThreshold to customThreshold in public --- .../components/alert_details_app_section.test.tsx | 10 +++++----- .../components/alert_details_app_section.tsx | 10 +++++----- .../custom_threshold/helpers/use_alert_prefill.ts | 4 ++-- .../hooks/use_metric_threshold_alert_prefill.ts | 2 +- .../lib/transform_metrics_explorer_data.ts | 4 ++-- ..._threshold_rule.ts => custom_threshold_rule.ts} | 14 +++++++------- .../public/components/custom_threshold/types.ts | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) rename x-pack/plugins/observability/public/components/custom_threshold/mocks/{metric_threshold_rule.ts => custom_threshold_rule.ts} (94%) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx index 2e035cdcf8fb8..d1ab267f1ce23 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx @@ -13,9 +13,9 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { - buildMetricThresholdAlert, - buildMetricThresholdRule, -} from '../mocks/metric_threshold_rule'; + buildCustomThresholdAlert, + buildCustomThresholdRule, +} from '../mocks/custom_threshold_rule'; import AlertDetailsAppSection from './alert_details_app_section'; import { ExpressionChart } from './expression_chart'; @@ -59,8 +59,8 @@ describe('AlertDetailsAppSection', () => { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx index 11a85e6b17bc2..5e4d3f99f0fab 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx @@ -37,19 +37,19 @@ import { ExpressionChart } from './expression_chart'; import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; import { Threshold } from './custom_threshold'; import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options'; -import { AlertParams, MetricThresholdRuleTypeParams } from '../types'; +import { AlertParams, CustomThresholdRuleTypeParams } from '../types'; // TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 -export type MetricThresholdRule = Rule; -export type MetricThresholdAlert = TopAlert; +export type CustomThresholdRule = Rule; +export type CustomThresholdAlert = TopAlert; 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; + alert: CustomThresholdAlert; + rule: CustomThresholdRule; ruleLink: string; setAlertSummaryFields: React.Dispatch>; } diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts index 3f659b1041762..d0f984da01441 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts @@ -6,10 +6,10 @@ */ import createContainer from 'constate'; -import { useMetricThresholdAlertPrefill } from '../hooks/use_metric_threshold_alert_prefill'; +import { useCustomThresholdAlertPrefill } from '../hooks/use_metric_threshold_alert_prefill'; const useAlertPrefill = () => { - const metricThresholdPrefill = useMetricThresholdAlertPrefill(); + const metricThresholdPrefill = useCustomThresholdAlertPrefill(); return { metricThresholdPrefill }; }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts index 6314ddb20a115..6eba9bc7f796a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts @@ -15,7 +15,7 @@ export interface MetricThresholdPrefillOptions { metrics: MetricsExplorerMetric[]; } -export const useMetricThresholdAlertPrefill = () => { +export const useCustomThresholdAlertPrefill = () => { const [prefillOptionsState, setPrefillOptionsState] = useState({ groupBy: undefined, filterQuery: undefined, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts index 2a30136924420..116fd86cefbfe 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts @@ -7,10 +7,10 @@ import { first } from 'lodash'; import { MetricsExplorerResponse } from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { MetricThresholdAlertParams, ExpressionChartSeries } from '../types'; +import { CustomThresholdAlertParams, ExpressionChartSeries } from '../types'; export const transformMetricsExplorerData = ( - params: MetricThresholdAlertParams, + params: CustomThresholdAlertParams, data: MetricsExplorerResponse | null ) => { const { criteria } = params; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts similarity index 94% rename from x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts rename to x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 4f2e4ea3772bc..9e77d916b9669 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -8,11 +8,11 @@ import { v4 as uuidv4 } from 'uuid'; import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; -import { MetricThresholdAlert, MetricThresholdRule } from '../components/alert_details_app_section'; +import { CustomThresholdAlert, CustomThresholdRule } from '../components/alert_details_app_section'; -export const buildMetricThresholdRule = ( - rule: Partial = {} -): MetricThresholdRule => { +export const buildCustomThresholdRule = ( + rule: Partial = {} +): CustomThresholdRule => { return { alertTypeId: 'metrics.alert.threshold', createdBy: 'admin', @@ -126,9 +126,9 @@ export const buildMetricThresholdRule = ( }; }; -export const buildMetricThresholdAlert = ( - alert: Partial = {} -): MetricThresholdAlert => { +export const buildCustomThresholdAlert = ( + alert: Partial = {} +): CustomThresholdAlert => { return { link: '/app/metrics/explorer', reason: 'system.cpu.user.pct reported no data in the last 1m for ', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts index d0b173b4d7c34..fbf6978849e0c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -68,7 +68,7 @@ export enum AGGREGATION_TYPES { CUSTOM = 'custom', } -export interface MetricThresholdAlertParams { +export interface CustomThresholdAlertParams { criteria?: MetricExpression[]; groupBy?: string | string[]; filterQuery?: string; @@ -173,7 +173,7 @@ export interface InventoryMetricConditions { warningComparator?: Comparator; } -export interface MetricThresholdRuleTypeParams extends RuleTypeParams { +export interface CustomThresholdRuleTypeParams extends RuleTypeParams { criteria: MetricExpressionParams[]; searchConfiguration: SerializedSearchSourceFields; groupBy?: string | string[]; From babe85047e93fa087b53f963c459e84870c2921e Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Fri, 27 Oct 2023 11:54:11 +0200 Subject: [PATCH 02/17] Rename metricThreshold to customThreshold in public folder --- .../components/custom_threshold/components/alert_flyout.tsx | 4 ++-- .../custom_equation/custom_equation_editor.stories.tsx | 4 ++-- .../components/custom_threshold/components/validation.tsx | 2 +- .../custom_threshold/helpers/use_alert_prefill.ts | 4 ++-- .../hooks/use_metric_threshold_alert_prefill.ts | 6 +++--- .../hooks/use_metrics_explorer_chart_data.ts | 4 ++-- .../hooks/use_metrics_explorer_options.test.tsx | 2 +- .../custom_threshold/hooks/use_metrics_explorer_options.ts | 6 +++--- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx index ba39bcd7c4861..d7cfb996f6c9b 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx @@ -48,8 +48,8 @@ export function AlertFlyout(props: Props) { } export function PrefilledThresholdAlertFlyout({ onClose }: { onClose(): void }) { - const { metricThresholdPrefill } = useAlertPrefillContext(); - const { groupBy, filterQuery, metrics } = metricThresholdPrefill; + const { customThresholdPrefill } = useAlertPrefillContext(); + const { groupBy, filterQuery, metrics } = customThresholdPrefill; return ; } diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx index f58894e7918d9..1b5c5e7ba375c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -19,7 +19,7 @@ import { TimeUnitChar } from '../../../../../common'; import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; import { aggregationType } from '../expression_row'; import { MetricExpression } from '../../types'; -import { validateMetricThreshold } from '../validation'; +import { validateCustomThreshold } from '../validation'; export default { title: 'app/Alerts/CustomEquationEditor', @@ -67,7 +67,7 @@ const CustomEquationEditorTemplate: Story = (args) => ); useEffect(() => { - const validationObject = validateMetricThreshold({ + const validationObject = validateCustomThreshold({ criteria: [expression as MetricExpressionParams], searchConfiguration: {}, }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx index d40a1f2852bcc..8c6d97413dea8 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -25,7 +25,7 @@ const isCustomMetricExpressionParams = ( return subject.aggType === Aggregators.CUSTOM; }; -export function validateMetricThreshold({ +export function validateCustomThreshold({ criteria, searchConfiguration, }: { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts index d0f984da01441..82a46eac7d01a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts @@ -9,8 +9,8 @@ import createContainer from 'constate'; import { useCustomThresholdAlertPrefill } from '../hooks/use_metric_threshold_alert_prefill'; const useAlertPrefill = () => { - const metricThresholdPrefill = useCustomThresholdAlertPrefill(); - return { metricThresholdPrefill }; + const customThresholdPrefill = useCustomThresholdAlertPrefill(); + return { customThresholdPrefill }; }; export const [AlertPrefillProvider, useAlertPrefillContext] = createContainer(useAlertPrefill); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts index 6eba9bc7f796a..5270f13dce1d8 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts @@ -9,14 +9,14 @@ import { isEqual } from 'lodash'; import { useState } from 'react'; import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; -export interface MetricThresholdPrefillOptions { +export interface CustomThresholdPrefillOptions { groupBy: string | string[] | undefined; filterQuery: string | undefined; metrics: MetricsExplorerMetric[]; } export const useCustomThresholdAlertPrefill = () => { - const [prefillOptionsState, setPrefillOptionsState] = useState({ + const [prefillOptionsState, setPrefillOptionsState] = useState({ groupBy: undefined, filterQuery: undefined, metrics: [], @@ -28,7 +28,7 @@ export const useCustomThresholdAlertPrefill = () => { groupBy, filterQuery, metrics, - setPrefillOptions(newState: MetricThresholdPrefillOptions) { + setPrefillOptions(newState: CustomThresholdPrefillOptions) { if (!isEqual(newState, prefillOptionsState)) setPrefillOptionsState(newState); }, }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts index ef810bc8857d9..f6ef1a8282224 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -43,7 +43,7 @@ export const useMetricsExplorerChartData = ( ? { aggregation: 'custom', custom_metrics: - expression?.metrics?.map(mapMetricThresholdMetricToMetricsExplorerMetric) ?? [], + expression?.metrics?.map(mapCustomThresholdMetricToMetricsExplorerMetric) ?? [], equation: expression.equation, } : { field: expression.metric, aggregation: expression.aggType }, @@ -77,7 +77,7 @@ export const useMetricsExplorerChartData = ( return useMetricsExplorerData(options, derivedIndexPattern, timestamps); }; -const mapMetricThresholdMetricToMetricsExplorerMetric = ( +const mapCustomThresholdMetricToMetricsExplorerMetric = ( metric: CustomThresholdExpressionMetric ) => { if (metric.aggType === 'count') { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx index 9def4a44d6a8e..23d97d0d1cae8 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx @@ -17,7 +17,7 @@ import { let PREFILL: Record = {}; jest.mock('../helpers/use_alert_prefill', () => ({ useAlertPrefillContext: () => ({ - metricThresholdPrefill: { + customThresholdPrefill: { setPrefillOptions(opts: Record) { PREFILL = opts; }, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts index 7d734a961b04e..f03ea875e5957 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts @@ -198,10 +198,10 @@ export const useMetricsExplorerOptions = () => { ); const [isAutoReloading, setAutoReloading] = useState(false); - const { metricThresholdPrefill } = useAlertPrefillContext(); - // For Jest compatibility; including metricThresholdPrefill as a dep in useEffect causes an + const { customThresholdPrefill } = useAlertPrefillContext(); + // For Jest compatibility; including customThresholdPrefill as a dep in useEffect causes an // infinite loop in test environment - const prefillContext = useMemo(() => metricThresholdPrefill, [metricThresholdPrefill]); + const prefillContext = useMemo(() => customThresholdPrefill, [customThresholdPrefill]); useEffect(() => { if (prefillContext) { From 7385077f3b26cd68382711dcfc7c4e8991838768 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Fri, 27 Oct 2023 14:43:57 +0200 Subject: [PATCH 03/17] Rename metricThreshold and remove unused types --- .../common/custom_threshold_rule/constants.ts | 1 - .../custom_threshold_rule/formatters/index.ts | 2 +- .../common/custom_threshold_rule/types.ts | 47 ------------------- .../criterion_preview_chart.tsx | 11 ++++- .../components/custom_threshold/types.ts | 14 ------ .../register_observability_rule_types.ts | 4 +- .../custom_threshold/lib/metrics_explorer.ts | 4 -- 7 files changed, 12 insertions(+), 71 deletions(-) diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts index 15886482e4b31..ae12e9b47c64b 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts @@ -5,7 +5,6 @@ * 2.0. */ -export const SNAPSHOT_CUSTOM_AGGREGATIONS = ['avg', 'max', 'min', 'rate'] as const; export const METRIC_EXPLORER_AGGREGATIONS = [ 'avg', 'max', diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts index f7491ca139bfe..00ecdef67d97f 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts @@ -14,7 +14,7 @@ import { InfraWaffleMapDataFormat } from './types'; export const FORMATTERS = { number: formatNumber, - // Because the implimentation for formatting large numbers is the same as formatting + // Because the implementation for formatting large numbers is the same as formatting // bytes we are re-using the same code, we just format the number using the abbreviated number format. abbreviatedNumber: createBytesFormatter(InfraWaffleMapDataFormat.abbreviatedNumber), // bytes in bytes formatted string out diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index dc5b434e0a7e9..f6b6f26c6a7be 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -12,7 +12,6 @@ import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { Color } from './color_palette'; import { metricsExplorerMetricRT } from './metrics_explorer'; import { TimeUnitChar } from '../utils/formatters/duration'; -import { SNAPSHOT_CUSTOM_AGGREGATIONS } from './constants'; type DeepPartialArray = Array>; @@ -33,32 +32,6 @@ export const ThresholdFormatterTypeRT = rt.keyof({ }); export type ThresholdFormatterType = rt.TypeOf; -const pointRT = rt.type({ - timestamp: rt.number, - value: rt.number, -}); - -export type Point = rt.TypeOf; - -const serieRT = rt.type({ - id: rt.string, - points: rt.array(pointRT), -}); - -const seriesRT = rt.array(serieRT); - -export type Series = rt.TypeOf; - -export const getLogAlertsChartPreviewDataSuccessResponsePayloadRT = rt.type({ - data: rt.type({ - series: seriesRT, - }), -}); - -export type GetLogAlertsChartPreviewDataSuccessResponsePayload = rt.TypeOf< - typeof getLogAlertsChartPreviewDataSuccessResponsePayloadRT ->; - /** * Properties specific to the Metrics Source Configuration. */ @@ -268,23 +241,3 @@ export enum InfraFormatterType { bits = 'bits', percent = 'percent', } - -export type SnapshotCustomAggregation = typeof SNAPSHOT_CUSTOM_AGGREGATIONS[number]; -const snapshotCustomAggregationKeys = SNAPSHOT_CUSTOM_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const SnapshotCustomAggregationRT = rt.keyof(snapshotCustomAggregationKeys); - -export const SnapshotCustomMetricInputRT = rt.intersection([ - rt.type({ - type: rt.literal('custom'), - field: rt.string, - aggregation: SnapshotCustomAggregationRT, - id: rt.string, - }), - rt.partial({ - label: rt.string, - }), -]); -export type SnapshotCustomMetricInput = rt.TypeOf; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/criterion_preview_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/criterion_preview_chart.tsx index a5ed7cd38c580..5669f7a190f81 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/criterion_preview_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/criterion_preview_chart.tsx @@ -12,10 +12,17 @@ import { i18n } from '@kbn/i18n'; import { EuiLoadingChart, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { sum, min as getMin, max as getMax } from 'lodash'; -import { GetLogAlertsChartPreviewDataSuccessResponsePayload } from '../../../../../common/custom_threshold_rule/types'; import { formatNumber } from '../../../../../common/custom_threshold_rule/formatters/number'; -type Series = GetLogAlertsChartPreviewDataSuccessResponsePayload['data']['series']; +interface Point { + timestamp: number; + value: number; +} + +type Series = Array<{ + id: string; + points: Point[]; +}>; export const NUM_BUCKETS = 20; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts index fbf6978849e0c..13a6e58aded98 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -24,15 +24,12 @@ import { import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { TimeUnitChar } from '../../../common/utils/formatters'; import { MetricsExplorerSeries } from '../../../common/custom_threshold_rule/metrics_explorer'; import { - Comparator, CustomMetricExpressionParams, MetricExpressionParams, MetricsSourceStatus, NonCountMetricExpressionParams, - SnapshotCustomMetricInput, } from '../../../common/custom_threshold_rule/types'; import { ObservabilityPublicStart } from '../../plugin'; import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; @@ -161,17 +158,6 @@ export const SnapshotMetricTypeKeys = { export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); export type SnapshotMetricType = rt.TypeOf; -export interface InventoryMetricConditions { - metric: SnapshotMetricType; - timeSize: number; - timeUnit: TimeUnitChar; - sourceId?: string; - threshold: number[]; - comparator: Comparator; - customMetric?: SnapshotCustomMetricInput; - warningThreshold?: number[]; - warningComparator?: Comparator; -} export interface CustomThresholdRuleTypeParams extends RuleTypeParams { criteria: MetricExpressionParams[]; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index 4cd705656c01b..4ae8f7f172076 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -14,7 +14,7 @@ import { ConfigSchema } from '../plugin'; import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; -import { validateMetricThreshold } from '../components/custom_threshold/components/validation'; +import { validateCustomThreshold } from '../components/custom_threshold/components/validation'; import { formatReason } from '../components/custom_threshold/rule_data_formatters'; const sloBurnRateDefaultActionMessage = i18n.translate( @@ -117,7 +117,7 @@ export const registerObservabilityRuleTypes = ( ruleParamsExpression: lazy( () => import('../components/custom_threshold/custom_threshold_rule_expression') ), - validate: validateMetricThreshold, + validate: validateCustomThreshold, defaultActionMessage: thresholdDefaultActionMessage, defaultRecoveryMessage: thresholdDefaultRecoveryMessage, requiresAppContext: false, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts index e72974828b027..03b494ba41fd2 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts @@ -118,12 +118,8 @@ export const metricsExplorerResponseRT = rt.type({ export type AfterKey = rt.TypeOf; -export type MetricsExplorerColumnType = rt.TypeOf; - export type MetricsExplorerPageInfo = rt.TypeOf; -export type MetricsExplorerColumn = rt.TypeOf; - export type MetricsExplorerRow = rt.TypeOf; export type MetricsExplorerRequestBody = rt.TypeOf; From b23afdf55ca9aaabb9927b983f93b8afe4ec1ef6 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Fri, 27 Oct 2023 17:28:56 +0200 Subject: [PATCH 04/17] Remove extra logic related to aggregations other than custom for default level. --- .../custom_threshold_rule/metrics_explorer.ts | 4 - .../lib/check_missing_group.ts | 4 +- .../lib/create_custom_metrics_aggregations.ts | 13 +- .../lib/create_rate_aggregation.ts | 66 --------- .../custom_threshold/lib/evaluate_rule.ts | 10 +- .../rules/custom_threshold/lib/get_data.ts | 78 ++--------- .../custom_threshold/lib/metric_query.test.ts | 69 +++++++++- .../custom_threshold/lib/metric_query.ts | 61 ++------- .../custom_threshold/lib/metrics_explorer.ts | 127 ------------------ 9 files changed, 88 insertions(+), 344 deletions(-) delete mode 100644 x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts delete mode 100644 x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts index 66913385123c4..e7cf25a0b89ea 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts @@ -137,14 +137,10 @@ export type AfterKey = rt.TypeOf; export type MetricsExplorerAggregation = rt.TypeOf; -export type MetricsExplorerColumnType = rt.TypeOf; - export type MetricsExplorerMetric = rt.TypeOf; export type MetricsExplorerPageInfo = rt.TypeOf; -export type MetricsExplorerColumn = rt.TypeOf; - export type MetricsExplorerRow = rt.TypeOf; export type MetricsExplorerSeries = rt.TypeOf; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts index 7651130601b6c..6b8041b448484 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts @@ -8,7 +8,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { isString, get, identity } from 'lodash'; -import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; +import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import type { BucketKey } from './get_data'; import { calculateCurrentTimeframe, createBaseFilters } from './metric_query'; @@ -19,7 +19,7 @@ export interface MissingGroupsRecord { export const checkMissingGroups = async ( esClient: ElasticsearchClient, - metricParams: MetricExpressionParams, + metricParams: CustomMetricExpressionParams, indexPattern: string, timeFieldName: string, groupBy: string | undefined | string[], diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts index 770d16c927e23..255475c8d8a5c 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts @@ -8,25 +8,16 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; import { CustomThresholdExpressionMetric } from '../../../../../common/custom_threshold_rule/types'; -import { MetricsExplorerCustomMetric } from './metrics_explorer'; - -const isMetricExpressionCustomMetric = ( - subject: MetricsExplorerCustomMetric | CustomThresholdExpressionMetric -): subject is CustomThresholdExpressionMetric => { - return 'aggType' in subject; -}; export const createCustomMetricsAggregations = ( id: string, - customMetrics: Array, + customMetrics: CustomThresholdExpressionMetric[], equation?: string ) => { const bucketsPath: { [id: string]: string } = {}; const metricAggregations = customMetrics.reduce((acc, metric) => { const key = `${id}_${metric.name}`; - const aggregation = isMetricExpressionCustomMetric(metric) - ? metric.aggType - : metric.aggregation; + const aggregation = metric.aggType; if (aggregation === 'count') { bucketsPath[metric.name] = `${key}>_count`; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts deleted file mode 100644 index 3017d7bc95dcd..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts +++ /dev/null @@ -1,66 +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 moment from 'moment'; -import { calculateRateTimeranges } from '../utils'; - -export const createRateAggsBucketScript = ( - timeframe: { start: number; end: number }, - id: string -) => { - const { intervalInSeconds } = calculateRateTimeranges({ - to: timeframe.end, - from: timeframe.start, - }); - return { - [id]: { - bucket_script: { - buckets_path: { - first: `currentPeriod['all']>${id}_first_bucket.maxValue`, - second: `currentPeriod['all']>${id}_second_bucket.maxValue`, - }, - script: `params.second > 0.0 && params.first > 0.0 && params.second > params.first ? (params.second - params.first) / ${intervalInSeconds}: null`, - }, - }, - }; -}; - -export const createRateAggsBuckets = ( - timeframe: { start: number; end: number; timeFieldName: string }, - id: string, - field: string -) => { - const { firstBucketRange, secondBucketRange } = calculateRateTimeranges({ - to: timeframe.end, - from: timeframe.start, - }); - - return { - [`${id}_first_bucket`]: { - filter: { - range: { - [timeframe.timeFieldName]: { - gte: moment(firstBucketRange.from).toISOString(), - lt: moment(firstBucketRange.to).toISOString(), - }, - }, - }, - aggs: { maxValue: { max: { field } } }, - }, - [`${id}_second_bucket`]: { - filter: { - range: { - [timeframe.timeFieldName]: { - gte: moment(secondBucketRange.from).toISOString(), - lt: moment(secondBucketRange.to).toISOString(), - }, - }, - }, - aggs: { maxValue: { max: { field } } }, - }, - }; -}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 30e3033d16737..513ce51f0fd4c 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -27,10 +27,9 @@ import { SearchConfigurationType } from '../types'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; import { checkMissingGroups, MissingGroupsRecord } from './check_missing_group'; -import { isCustom } from './metric_expression_params'; export interface EvaluatedRuleParams { - criteria: MetricExpressionParams[]; + criteria: CustomMetricExpressionParams[]; groupBy: string | undefined | string[]; searchConfiguration: SearchConfigurationType; } @@ -133,12 +132,7 @@ export const evaluateRule = async { }; } -const getValue = (aggregatedValue: AggregatedValue, params: MetricExpressionParams) => - [Aggregators.P95, Aggregators.P99].includes(params.aggType) && aggregatedValue.values != null - ? aggregatedValue.values[params.aggType === Aggregators.P95 ? '95.0' : '99.0'] - : aggregatedValue.value; - const NO_DATA_RESPONSE = { [UNGROUPED_FACTORY_KEY]: { value: null, @@ -105,7 +96,7 @@ const createContainerList = (containerContext: ContainerContext) => { export const getData = async ( esClient: ElasticsearchClient, - params: MetricExpressionParams, + params: CustomMetricExpressionParams, index: string, timeFieldName: string, groupBy: string | undefined | string[], @@ -132,16 +123,10 @@ export const getData = async ( const nextAfterKey = groupings.after_key; for (const bucket of groupings.buckets) { const key = Object.values(bucket.key).join(','); - const { - shouldTrigger, - missingGroup, - currentPeriod, - aggregatedValue: aggregatedValueForRate, - additionalContext, - containerContext, - } = bucket; + const { shouldTrigger, missingGroup, currentPeriod, additionalContext, containerContext } = + bucket; - const { aggregatedValue, doc_count: docCount } = currentPeriod.buckets.all; + const { aggregatedValue } = currentPeriod.buckets.all; const containerList = containerContext ? createContainerList(containerContext) : undefined; @@ -156,14 +141,7 @@ export const getData = async ( bucketKey: bucket.key, }; } else { - const value = - params.aggType === Aggregators.COUNT - ? docCount - : params.aggType === Aggregators.RATE && aggregatedValueForRate != null - ? aggregatedValueForRate.value - : aggregatedValue != null - ? getValue(aggregatedValue, params) - : null; + const value = aggregatedValue ? aggregatedValue.value : null; previous[key] = { trigger: (shouldTrigger && shouldTrigger.value > 0) || false, @@ -194,36 +172,10 @@ export const getData = async ( return previous; } if (aggs.all?.buckets.all) { - const { - currentPeriod, - aggregatedValue: aggregatedValueForRate, - shouldTrigger, - } = aggs.all.buckets.all; - - const { aggregatedValue, doc_count: docCount } = currentPeriod.buckets.all; + const { currentPeriod, shouldTrigger } = aggs.all.buckets.all; - const value = - params.aggType === Aggregators.COUNT - ? docCount - : params.aggType === Aggregators.RATE && aggregatedValueForRate != null - ? aggregatedValueForRate.value - : aggregatedValue != null - ? getValue(aggregatedValue, params) - : null; - // There is an edge case where there is no results and the shouldTrigger - // bucket scripts will be missing. This is only an issue for document count because - // the value will end up being ZERO, for other metrics it will be null. In this case - // we need to do the evaluation in Node.js - if (aggs.all && params.aggType === Aggregators.COUNT && value === 0) { - const trigger = comparatorMap[params.comparator](value, params.threshold); - return { - [UNGROUPED_FACTORY_KEY]: { - value, - trigger, - bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY }, - }, - }; - } + const { aggregatedValue } = currentPeriod.buckets.all; + const value = aggregatedValue ? aggregatedValue.value : null; return { [UNGROUPED_FACTORY_KEY]: { value, @@ -268,15 +220,3 @@ export const getData = async ( } return NO_DATA_RESPONSE; }; - -const comparatorMap = { - [Comparator.BETWEEN]: (value: number, [a, b]: number[]) => - value >= Math.min(a, b) && value <= Math.max(a, b), - // `threshold` is always an array of numbers in case the BETWEEN comparator is - // used; all other compartors will just destructure the first value in the array - [Comparator.GT]: (a: number, [b]: number[]) => a > b, - [Comparator.LT]: (a: number, [b]: number[]) => a < b, - [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b, - [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b, - [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b, -}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts index cdfe0c66c0ab9..5e3eaf1168ebe 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts @@ -8,15 +8,21 @@ import { Comparator, Aggregators, - MetricExpressionParams, + CustomMetricExpressionParams, } from '../../../../../common/custom_threshold_rule/types'; import moment from 'moment'; import { getElasticsearchMetricQuery } from './metric_query'; describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { - const expressionParams: MetricExpressionParams = { - metric: 'system.is.a.good.puppy.dog', - aggType: Aggregators.AVERAGE, + const expressionParams: CustomMetricExpressionParams = { + metrics: [ + { + name: 'A', + aggType: Aggregators.AVERAGE, + field: 'system.is.a.good.puppy.dog', + }, + ], + aggType: Aggregators.CUSTOM, timeUnit: 'm', timeSize: 1, threshold: [1], @@ -47,8 +53,28 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { }); test('includes a metric field filter', () => { - expect(searchBody.query.bool.filter).toMatchObject( - expect.arrayContaining([{ exists: { field: 'system.is.a.good.puppy.dog' } }]) + expect(searchBody.aggs.groupings.aggs.currentPeriod).toMatchObject( + expect.objectContaining({ + aggs: { + // eslint-disable-next-line @typescript-eslint/naming-convention + aggregatedValue_A: { + avg: { + field: 'system.is.a.good.puppy.dog', + }, + }, + aggregatedValue: { + bucket_script: { + buckets_path: { + A: 'aggregatedValue_A', + }, + script: { + source: 'params.A', + lang: 'painless', + }, + }, + }, + }, + }) ); }); }); @@ -79,7 +105,6 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { expect(searchBody.query.bool.filter).toMatchObject( expect.arrayContaining([ { range: { mockedTimeFieldName: expect.any(Object) } }, - { exists: { field: 'system.is.a.good.puppy.dog' } }, { bool: { filter: [ @@ -108,6 +133,36 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { }, ]) ); + expect(searchBody.aggs.groupings.aggs).toMatchObject( + expect.objectContaining({ + currentPeriod: { + filters: { + filters: { + all: { range: { mockedTimeFieldName: expect.any(Object) } }, + }, + }, + aggs: { + // eslint-disable-next-line @typescript-eslint/naming-convention + aggregatedValue_A: { + avg: { + field: 'system.is.a.good.puppy.dog', + }, + }, + aggregatedValue: { + bucket_script: { + buckets_path: { + A: 'aggregatedValue_A', + }, + script: { + source: 'params.A', + lang: 'painless', + }, + }, + }, + }, + }, + }) + ); }); }); }); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index 66868ad97dfc9..fa54f07ea8c23 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -6,11 +6,8 @@ */ import moment from 'moment'; -import { - Aggregators, - MetricExpressionParams, -} from '../../../../../common/custom_threshold_rule/types'; -import { isCustom, isNotCountOrCustom } from './metric_expression_params'; +import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; +import { isNotCountOrCustom } from './metric_expression_params'; import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; import { CONTAINER_ID, @@ -20,26 +17,19 @@ import { validGroupByForContext, } from '../utils'; import { createBucketSelector } from './create_bucket_selector'; -import { createPercentileAggregation } from './create_percentile_aggregation'; -import { createRateAggsBuckets, createRateAggsBucketScript } from './create_rate_aggregation'; import { wrapInCurrentPeriod } from './wrap_in_period'; import { getParsedFilterQuery } from '../../../../utils/get_parsed_filtered_query'; export const calculateCurrentTimeframe = ( - metricParams: MetricExpressionParams, + metricParams: CustomMetricExpressionParams, timeframe: { start: number; end: number } ) => ({ ...timeframe, - start: moment(timeframe.end) - .subtract( - metricParams.aggType === Aggregators.RATE ? metricParams.timeSize * 2 : metricParams.timeSize, - metricParams.timeUnit - ) - .valueOf(), + start: moment(timeframe.end).subtract(metricParams.timeSize, metricParams.timeUnit).valueOf(), }); export const createBaseFilters = ( - metricParams: MetricExpressionParams, + metricParams: CustomMetricExpressionParams, timeframe: { start: number; end: number }, timeFieldName: string, filterQuery?: string @@ -72,7 +62,7 @@ export const createBaseFilters = ( }; export const getElasticsearchMetricQuery = ( - metricParams: MetricExpressionParams, + metricParams: CustomMetricExpressionParams, timeframe: { start: number; end: number }, timeFieldName: string, compositeSize: number, @@ -83,13 +73,6 @@ export const getElasticsearchMetricQuery = ( afterKey?: Record, fieldsExisted?: Record | null ) => { - const { aggType } = metricParams; - if (isNotCountOrCustom(metricParams) && !metricParams.metric) { - throw new Error( - 'Can only aggregate without a metric if using the document count or custom aggregator' - ); - } - // We need to make a timeframe that represents the current timeframe as opposed // to the total timeframe (which includes the last period). const currentTimeframe = { @@ -97,26 +80,11 @@ export const getElasticsearchMetricQuery = ( timeFieldName, }; - const metricAggregations = - aggType === Aggregators.COUNT - ? {} - : aggType === Aggregators.RATE - ? createRateAggsBuckets(currentTimeframe, 'aggregatedValue', metricParams.metric) - : aggType === Aggregators.P95 || aggType === Aggregators.P99 - ? createPercentileAggregation(aggType, metricParams.metric) - : isCustom(metricParams) - ? createCustomMetricsAggregations( - 'aggregatedValue', - metricParams.metrics, - metricParams.equation - ) - : { - aggregatedValue: { - [aggType]: { - field: metricParams.metric, - }, - }, - }; + const metricAggregations = createCustomMetricsAggregations( + 'aggregatedValue', + metricParams.metrics, + metricParams.equation + ); const bucketSelectorAggregations = createBucketSelector( metricParams, @@ -126,11 +94,6 @@ export const getElasticsearchMetricQuery = ( lastPeriodEnd ); - const rateAggBucketScript = - metricParams.aggType === Aggregators.RATE - ? createRateAggsBucketScript(currentTimeframe, 'aggregatedValue') - : {}; - const currentPeriod = wrapInCurrentPeriod(currentTimeframe, metricAggregations); const containerIncludesList = ['container.*']; @@ -207,7 +170,6 @@ export const getElasticsearchMetricQuery = ( }, aggs: { ...currentPeriod, - ...rateAggBucketScript, ...bucketSelectorAggregations, ...additionalContextAgg, ...containerContextAgg, @@ -225,7 +187,6 @@ export const getElasticsearchMetricQuery = ( }, aggs: { ...currentPeriod, - ...rateAggBucketScript, ...bucketSelectorAggregations, }, }, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts deleted file mode 100644 index 03b494ba41fd2..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts +++ /dev/null @@ -1,127 +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 * as rt from 'io-ts'; -import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../../common/custom_threshold_rule/constants'; -import { metricsExplorerCustomMetricAggregationRT } from '../../../../../common/custom_threshold_rule/metrics_explorer'; - -type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; - -const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); - -export const metricsExplorerMetricRequiredFieldsRT = rt.type({ - aggregation: metricsExplorerAggregationRT, -}); - -export const metricsExplorerCustomMetricRT = rt.intersection([ - rt.type({ - name: rt.string, - aggregation: metricsExplorerCustomMetricAggregationRT, - }), - rt.partial({ - field: rt.string, - filter: rt.string, - }), -]); - -export type MetricsExplorerCustomMetric = rt.TypeOf; - -export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ - field: rt.union([rt.string, rt.undefined]), - custom_metrics: rt.array(metricsExplorerCustomMetricRT), - equation: rt.string, -}); - -export const metricsExplorerMetricRT = rt.intersection([ - metricsExplorerMetricRequiredFieldsRT, - metricsExplorerMetricOptionalFieldsRT, -]); - -export const timeRangeRT = rt.type({ - from: rt.number, - to: rt.number, - interval: rt.string, -}); - -export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ - timerange: timeRangeRT, - indexPattern: rt.string, - metrics: rt.array(metricsExplorerMetricRT), -}); - -const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); -export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); - -export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ - groupBy: rt.union([groupByRT, rt.array(groupByRT)]), - afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), - limit: rt.union([rt.number, rt.null, rt.undefined]), - filterQuery: rt.union([rt.string, rt.null, rt.undefined]), - forceInterval: rt.boolean, - dropLastBucket: rt.boolean, -}); - -export const metricsExplorerRequestBodyRT = rt.intersection([ - metricsExplorerRequestBodyRequiredFieldsRT, - metricsExplorerRequestBodyOptionalFieldsRT, -]); - -export const metricsExplorerPageInfoRT = rt.type({ - total: rt.number, - afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), -}); - -export const metricsExplorerColumnTypeRT = rt.keyof({ - date: null, - number: null, - string: null, -}); - -export const metricsExplorerColumnRT = rt.type({ - name: rt.string, - type: metricsExplorerColumnTypeRT, -}); - -export const metricsExplorerRowRT = rt.intersection([ - rt.type({ - timestamp: rt.number, - }), - rt.record( - rt.string, - rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) - ), -]); - -export const metricsExplorerSeriesRT = rt.intersection([ - rt.type({ - id: rt.string, - columns: rt.array(metricsExplorerColumnRT), - rows: rt.array(metricsExplorerRowRT), - }), - rt.partial({ - keys: rt.array(rt.string), - }), -]); - -export const metricsExplorerResponseRT = rt.type({ - series: rt.array(metricsExplorerSeriesRT), - pageInfo: metricsExplorerPageInfoRT, -}); - -export type AfterKey = rt.TypeOf; - -export type MetricsExplorerPageInfo = rt.TypeOf; - -export type MetricsExplorerRow = rt.TypeOf; - -export type MetricsExplorerRequestBody = rt.TypeOf; - -export type MetricsExplorerResponse = rt.TypeOf; From 4b6b1faa6096c8a50e7d6b0ce4c2e7a343aee995 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 30 Oct 2023 11:19:07 +0100 Subject: [PATCH 05/17] Remove metric_expression_params functions --- .../lib/metric_expression_params.ts | 26 ------------------- .../custom_threshold/lib/metric_query.ts | 14 +--------- 2 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts deleted file mode 100644 index 278f33dadafe9..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts +++ /dev/null @@ -1,26 +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 { - CustomMetricExpressionParams, - MetricExpressionParams, - NonCountMetricExpressionParams, -} from '../../../../../common/custom_threshold_rule/types'; - -export const isNotCountOrCustom = ( - metricExpressionParams: MetricExpressionParams -): metricExpressionParams is NonCountMetricExpressionParams => { - const { aggType } = metricExpressionParams; - return aggType !== 'count' && aggType !== 'custom'; -}; - -export const isCustom = ( - metricExpressionParams: MetricExpressionParams -): metricExpressionParams is CustomMetricExpressionParams => { - const { aggType } = metricExpressionParams; - return aggType === 'custom'; -}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index fa54f07ea8c23..6471926522929 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -7,7 +7,6 @@ import moment from 'moment'; import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; -import { isNotCountOrCustom } from './metric_expression_params'; import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; import { CONTAINER_ID, @@ -45,20 +44,9 @@ export const createBaseFilters = ( }, ]; - const metricFieldFilters = - isNotCountOrCustom(metricParams) && metricParams.metric - ? [ - { - exists: { - field: metricParams.metric, - }, - }, - ] - : []; - const parsedFilterQuery = getParsedFilterQuery(filterQuery); - return [...rangeFilters, ...metricFieldFilters, ...parsedFilterQuery]; + return [...rangeFilters, ...parsedFilterQuery]; }; export const getElasticsearchMetricQuery = ( From cf8e0f6be026c62f9ce7134271478c4aeb1467d4 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 30 Oct 2023 11:53:38 +0100 Subject: [PATCH 06/17] Remove extra validation for aggType custom --- .../components/validation.tsx | 112 +++++++----------- 1 file changed, 44 insertions(+), 68 deletions(-) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx index 8c6d97413dea8..143229f55062c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -10,21 +10,10 @@ import { buildEsQuery, fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { isEmpty } from 'lodash'; -import { - Aggregators, - Comparator, - CustomMetricExpressionParams, - MetricExpressionParams, -} from '../../../../common/custom_threshold_rule/types'; +import { Comparator, MetricExpressionParams } from '../../../../common/custom_threshold_rule/types'; export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; -const isCustomMetricExpressionParams = ( - subject: MetricExpressionParams -): subject is CustomMetricExpressionParams => { - return subject.aggType === Aggregators.CUSTOM; -}; - export function validateCustomThreshold({ criteria, searchConfiguration, @@ -187,66 +176,53 @@ export function validateCustomThreshold({ ); } - if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) { - errors[id].metric.push( - i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired', - { - defaultMessage: 'Metric is required.', - } - ) + if (!c.metrics || (c.metrics && c.metrics.length < 1)) { + errors[id].metricsError = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metricsError', + { + defaultMessage: 'You must define at least 1 custom metric', + } ); - } - - if (isCustomMetricExpressionParams(c)) { - if (!c.metrics || (c.metrics && c.metrics.length < 1)) { - errors[id].metricsError = i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.error.metricsError', - { - defaultMessage: 'You must define at least 1 custom metric', - } - ); - } else { - c.metrics.forEach((metric) => { - const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; - if (!metric.aggType) { - customMetricErrors.aggType = i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired', - { - defaultMessage: 'Aggregation is required', - } - ); - } - if (metric.aggType !== 'count' && !metric.field) { - customMetricErrors.field = i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired', - { - defaultMessage: 'Field is required', - } - ); - } - if (metric.aggType === 'count' && metric.filter) { - try { - fromKueryExpression(metric.filter); - } catch (e) { - customMetricErrors.filter = e.message; + } else { + c.metrics.forEach((metric) => { + const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; + if (!metric.aggType) { + customMetricErrors.aggType = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired', + { + defaultMessage: 'Aggregation is required', } + ); + } + if (metric.aggType !== 'count' && !metric.field) { + customMetricErrors.field = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired', + { + defaultMessage: 'Field is required', + } + ); + } + if (metric.aggType === 'count' && metric.filter) { + try { + fromKueryExpression(metric.filter); + } catch (e) { + customMetricErrors.filter = e.message; } - if (!isEmpty(customMetricErrors)) { - errors[id].metrics[metric.name] = customMetricErrors; - } - }); - } + } + if (!isEmpty(customMetricErrors)) { + errors[id].metrics[metric.name] = customMetricErrors; + } + }); + } - if (c.equation && c.equation.match(EQUATION_REGEX)) { - errors[id].equation = i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters', - { - defaultMessage: - 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', - } - ); - } + if (c.equation && c.equation.match(EQUATION_REGEX)) { + errors[id].equation = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters', + { + defaultMessage: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + ); } }); From 84ded2f81ddfd84ecac28b89754a1d2debc6bbb3 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 1 Nov 2023 12:01:07 +0100 Subject: [PATCH 07/17] Remove preFill logic and unnecessary types --- .../common/custom_threshold_rule/constants.ts | 4 - .../custom_threshold_rule/metrics_explorer.ts | 6 +- .../common/custom_threshold_rule/types.ts | 48 +- .../alert_details_app_section.test.tsx.snap | 8 +- .../components/alert_details_app_section.tsx | 19 +- .../components/alert_flyout.tsx | 16 +- .../custom_equation_editor.stories.tsx | 18 +- .../custom_equation_editor.tsx | 1 + .../components/custom_threshold.stories.tsx | 2 +- .../components/custom_threshold.test.tsx | 10 +- .../components/custom_threshold.tsx | 9 +- .../components/expression_chart.test.tsx | 22 +- .../components/expression_chart.tsx | 34 +- .../components/expression_row.test.tsx | 32 +- .../components/expression_row.tsx | 47 +- .../custom_threshold/components/group_by.tsx | 25 +- .../components/series_chart.tsx | 40 +- .../components/validation.tsx | 8 +- .../custom_threshold_rule_expression.test.tsx | 61 +- .../custom_threshold_rule_expression.tsx | 115 +--- .../helpers/calculate_domain.ts | 25 +- .../helpers/create_formatter_for_metric.ts | 18 +- .../create_formatter_for_metrics.test.ts | 30 +- .../helpers/create_metric_label.test.ts | 20 - .../helpers/create_metric_label.ts | 15 - .../custom_threshold/helpers/get_metric_id.ts | 12 - .../helpers/metric_to_format.ts | 11 +- .../helpers/use_alert_prefill.ts | 16 - ...t_data.ts => use_expression_chart_data.ts} | 34 +- ...xplorer_data.ts => use_expression_data.ts} | 14 +- .../use_metric_threshold_alert_prefill.ts | 16 +- .../hooks/use_metrics_explorer_data.test.tsx | 16 +- .../use_metrics_explorer_options.test.tsx | 127 ----- .../hooks/use_metrics_explorer_options.ts | 236 -------- .../lib/generate_unique_key.test.ts | 51 -- .../lib/generate_unique_key.ts | 14 - .../lib/transform_metrics_explorer_data.ts | 32 -- .../mocks/custom_threshold_rule.ts | 48 +- .../components/custom_threshold/types.ts | 113 ++-- .../public/utils/metrics_explorer.ts | 30 +- .../custom_threshold_executor.test.ts | 535 ++++++++++++------ .../custom_threshold_executor.ts | 4 +- .../lib/create_bucket_selector.ts | 20 +- .../custom_threshold/lib/evaluate_rule.ts | 39 +- .../lib/format_alert_result.ts | 24 +- .../custom_threshold/lib/wrap_in_period.ts | 4 +- .../lib/rules/custom_threshold/messages.ts | 41 +- 47 files changed, 719 insertions(+), 1351 deletions(-) delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts rename x-pack/plugins/observability/public/components/custom_threshold/hooks/{use_metrics_explorer_chart_data.ts => use_expression_chart_data.ts} (73%) rename x-pack/plugins/observability/public/components/custom_threshold/hooks/{use_metrics_explorer_data.ts => use_expression_data.ts} (87%) delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts delete mode 100644 x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts index ae12e9b47c64b..fa31ea50b69dd 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts @@ -10,10 +10,6 @@ export const METRIC_EXPLORER_AGGREGATIONS = [ 'max', 'min', 'cardinality', - 'rate', 'count', 'sum', - 'p95', - 'p99', - 'custom', ] as const; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts index e7cf25a0b89ea..89756f6bad264 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts @@ -49,11 +49,9 @@ export const metricsExplorerCustomMetricRT = rt.intersection([ }), ]); -export type MetricsExplorerCustomMetric = rt.TypeOf; - export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ field: rt.union([rt.string, rt.undefined]), - custom_metrics: rt.array(metricsExplorerCustomMetricRT), + metrics: rt.array(metricsExplorerCustomMetricRT), equation: rt.string, }); @@ -139,8 +137,6 @@ export type MetricsExplorerAggregation = rt.TypeOf; -export type MetricsExplorerPageInfo = rt.TypeOf; - export type MetricsExplorerRow = rt.TypeOf; export type MetricsExplorerSeries = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index f6b6f26c6a7be..8dc5c7e644a6f 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -6,7 +6,6 @@ */ import * as rt from 'io-ts'; -import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; import { values } from 'lodash'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { Color } from './color_palette'; @@ -73,30 +72,6 @@ export const logIndexNameReferenceRT = rt.type({ export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); -/** - * Source status - */ -const SourceStatusFieldRuntimeType = rt.type({ - name: rt.string, - type: rt.string, - searchable: rt.boolean, - aggregatable: rt.boolean, - displayable: rt.boolean, -}); -export const SourceStatusRuntimeType = rt.type({ - logIndicesExist: rt.boolean, - metricIndicesExist: rt.boolean, - remoteClustersExist: rt.boolean, - indexFields: rt.array(SourceStatusFieldRuntimeType), -}); -export const metricsSourceStatusRT = rt.strict({ - metricIndicesExist: SourceStatusRuntimeType.props.metricIndicesExist, - remoteClustersExist: SourceStatusRuntimeType.props.metricIndicesExist, - indexFields: SourceStatusRuntimeType.props.indexFields, -}); - -export type MetricsSourceStatus = rt.TypeOf; - export enum Comparator { GT = '>', LT = '<', @@ -149,27 +124,6 @@ export enum AlertStates { ERROR, } -const metricAnomalyNodeTypeRT = rt.union([rt.literal('hosts'), rt.literal('k8s')]); -const metricAnomalyMetricRT = rt.union([ - rt.literal('memory_usage'), - rt.literal('network_in'), - rt.literal('network_out'), -]); -const metricAnomalyInfluencerFilterRT = rt.type({ - fieldName: rt.string, - fieldValue: rt.string, -}); - -export interface MetricAnomalyParams { - nodeType: rt.TypeOf; - metric: rt.TypeOf; - alertInterval?: string; - sourceId?: string; - spaceId?: string; - threshold: Exclude; - influencerFilter: rt.TypeOf | undefined; -} - // Types for the executor export interface ThresholdParams { @@ -182,7 +136,7 @@ export interface ThresholdParams { groupBy?: string[]; } -interface BaseMetricExpressionParams { +export interface BaseMetricExpressionParams { timeSize: number; timeUnit: TimeUnitChar; sourceId?: string; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap index ca106766d89e9..2e12193435726 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap @@ -22,8 +22,14 @@ Array [ "title": "unknown-index", }, "expression": Object { - "aggType": "count", + "aggType": "custom", "comparator": ">", + "metrics": Array [ + Object { + "aggType": "count", + "name": "A", + }, + ], "threshold": Array [ 2000, ], diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx index 5e4d3f99f0fab..a5a029d764a89 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx @@ -28,15 +28,14 @@ import { AlertActiveTimeRangeAnnotation, } from '@kbn/observability-alert-details'; import { DataView } from '@kbn/data-views-plugin/common'; +import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; import { useKibana } from '../../../utils/kibana_react'; import { metricValueFormatter } from '../../../../common/custom_threshold_rule/metric_value_formatter'; import { AlertSummaryField, TopAlert } from '../../..'; -import { generateUniqueKey } from '../lib/generate_unique_key'; import { ExpressionChart } from './expression_chart'; import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; import { Threshold } from './custom_threshold'; -import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options'; import { AlertParams, CustomThresholdRuleTypeParams } from '../types'; // TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 @@ -132,13 +131,10 @@ export default function AlertDetailsAppSection({ const overview = !!ruleParams.criteria ? ( {ruleParams.criteria.map((criterion, index) => ( - + -

- {criterion.aggType.toUpperCase()}{' '} - {'metric' in criterion ? criterion.metric : undefined} -

+

{criterion.aggType.toUpperCase()}

- metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined) + metricValueFormatter( + d, + criterion.metrics[0] ? criterion.metrics[0].name : undefined + ) } title={i18n.translate( 'xpack.observability.customThreshold.rule.alertDetailsAppSection.thresholdTitle', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx index d7cfb996f6c9b..ba1f337cd6181 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx @@ -8,17 +8,11 @@ import React, { useCallback, useContext, useMemo } from 'react'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; - import { TriggerActionsContext } from './triggers_actions_context'; -import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; -import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; import { observabilityRuleCreationValidConsumers } from '../../../../common/constants'; interface Props { visible?: boolean; - options?: Partial; - series?: MetricsExplorerSeries; setVisible: React.Dispatch>; } @@ -34,13 +28,8 @@ export function AlertFlyout(props: Props) { onClose: onCloseFlyout, canChangeTrigger: false, ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - metadata: { - currentOptions: props.options, - series: props.series, - }, validConsumers: observabilityRuleCreationValidConsumers, }), - // eslint-disable-next-line react-hooks/exhaustive-deps [triggersActionsUI, onCloseFlyout] ); @@ -48,8 +37,5 @@ export function AlertFlyout(props: Props) { } export function PrefilledThresholdAlertFlyout({ onClose }: { onClose(): void }) { - const { customThresholdPrefill } = useAlertPrefillContext(); - const { groupBy, filterQuery, metrics } = customThresholdPrefill; - - return ; + return ; } diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx index 1b5c5e7ba375c..cc7422215551e 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -12,7 +12,7 @@ import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_util import { Aggregators, Comparator, - MetricExpressionParams, + CustomMetricExpressionParams, } from '../../../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../../../common'; @@ -68,7 +68,7 @@ const CustomEquationEditorTemplate: Story = (args) => useEffect(() => { const validationObject = validateCustomThreshold({ - criteria: [expression as MetricExpressionParams], + criteria: [expression as CustomMetricExpressionParams], searchConfiguration: {}, }); setErrors(validationObject.errors[0]); @@ -89,9 +89,15 @@ export const CustomEquationEditorDefault = CustomEquationEditorTemplate.bind({}) export const CustomEquationEditorWithEquationErrors = CustomEquationEditorTemplate.bind({}); export const CustomEquationEditorWithFieldError = CustomEquationEditorTemplate.bind({}); -const BASE_ARGS = { +const BASE_ARGS: Partial = { expression: { aggType: Aggregators.CUSTOM, + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + }, + ], timeSize: 1, timeUnit: 'm' as TimeUnitChar, threshold: [1], @@ -113,12 +119,16 @@ CustomEquationEditorDefault.args = { CustomEquationEditorWithEquationErrors.args = { ...BASE_ARGS, expression: { - ...BASE_ARGS.expression, + aggType: Aggregators.CUSTOM, equation: 'Math.round(A / B)', metrics: [ { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.user.pct' }, { name: 'B', aggType: Aggregators.MAX, field: 'system.cpu.cores' }, ], + timeSize: 1, + timeUnit: 'm' as TimeUnitChar, + threshold: [1], + comparator: Comparator.GT, }, errors: { equation: diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index c835f636eaa2d..f3635f39cf3bc 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { EuiFieldText, EuiFormRow, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx index 8e1e6b590e601..9598aabcbf70f 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.stories.tsx @@ -34,7 +34,7 @@ const defaultProps: Props = { chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, comparator: Comparator.GT, id: 'componentId', - threshold: 90, + threshold: [90], title: 'Threshold breached', value: 93, valueFormatter: (d) => `${d}%`, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx index a1d61e0ce28cd..7731792f7b322 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.test.tsx @@ -19,7 +19,7 @@ describe('Threshold', () => { chartProps: { theme: EUI_CHARTS_THEME_LIGHT.theme, baseTheme: LIGHT_THEME }, comparator: Comparator.GT, id: 'componentId', - threshold: 90, + threshold: [90], title: 'Threshold breached', value: 93, valueFormatter: (d) => `${d}%`, @@ -41,4 +41,12 @@ describe('Threshold', () => { const component = renderComponent(); expect(component.queryByTestId('thresholdRule-90-93')).toBeTruthy(); }); + + it('shows component for between', () => { + const component = renderComponent({ + comparator: Comparator.BETWEEN, + threshold: [90, 95], + }); + expect(component.queryByTestId('thresholdRule-90-95-93')).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx index 0dad6a2d98a18..5f28676eb37b6 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx @@ -21,7 +21,7 @@ export interface Props { chartProps: ChartProps; comparator: Comparator | string; id: string; - threshold: number; + threshold: number[]; title: string; value: number; valueFormatter: (d: number) => string; @@ -48,7 +48,7 @@ export function Threshold({ minWidth: '100%', }} hasShadow={false} - data-test-subj={`thresholdRule-${threshold}-${value}`} + data-test-subj={`thresholdRule-${threshold.join('-')}-${value}`} > @@ -63,7 +63,10 @@ export function Threshold({ {i18n.translate( 'xpack.observability.customThreshold.rule.thresholdExtraTitle', { - values: { comparator, threshold: valueFormatter(threshold) }, + values: { + comparator, + threshold: threshold.map((t) => valueFormatter(t)).join(' - '), + }, defaultMessage: `Alert when {comparator} {threshold}`, } )} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx index e9d6d2c726665..632fd030c95b3 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx @@ -5,16 +5,16 @@ * 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'; // 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 { DataViewBase } from '@kbn/es-query'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import React, { ReactElement } from 'react'; +import { act } from 'react-dom/test-utils'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../types'; import { ExpressionChart } from './expression_chart'; -import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; const mockStartServices = mockCoreMock.createStart(); @@ -37,8 +37,8 @@ const mockResponse = { series: [{ id: 'Everything', rows: [], columns: [] }], }; -jest.mock('../hooks/use_metrics_explorer_chart_data', () => ({ - useMetricsExplorerChartData: () => ({ loading: false, data: { pages: [mockResponse] } }), +jest.mock('../hooks/use_expression_chart_data', () => ({ + useExpressionChartData: () => ({ loading: false, data: { pages: [mockResponse] } }), })); describe('ExpressionChart', () => { @@ -76,7 +76,13 @@ describe('ExpressionChart', () => { it('should display no data message', async () => { const expression: MetricExpression = { - aggType: Aggregators.AVERAGE, + aggType: Aggregators.CUSTOM, + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + }, + ], timeSize: 1, timeUnit: 'm', sourceId: 'default', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx index e5e9f532b07d0..f236700f62b26 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx @@ -25,18 +25,12 @@ import { first, last } from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../utils/kibana_react'; -import { - MetricsExplorerAggregation, - MetricsExplorerRow, -} from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { MetricsExplorerRow } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { Color } from '../../../../common/custom_threshold_rule/color_palette'; -import { - MetricsExplorerChartType, - MetricsExplorerOptionsMetric, -} from '../../../../common/custom_threshold_rule/types'; +import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; import { createFormatterForMetric } from '../helpers/create_formatter_for_metric'; -import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; +import { useExpressionChartData } from '../hooks/use_expression_chart_data'; import { ChartContainer, LoadingState, @@ -47,7 +41,6 @@ import { import { ThresholdAnnotations } from './criterion_preview_chart/threshold_annotations'; import { CUSTOM_EQUATION } from '../i18n_strings'; import { calculateDomain } from '../helpers/calculate_domain'; -import { getMetricId } from '../helpers/get_metric_id'; import { MetricExplorerSeriesChart } from './series_chart'; interface Props { @@ -74,7 +67,7 @@ export function ExpressionChart({ timeFieldName, }: Props) { const { charts, uiSettings } = useKibana().services; - const { isLoading, data } = useMetricsExplorerChartData( + const { isLoading, data } = useExpressionChartData( expression, derivedIndexPattern, filterQuery, @@ -106,15 +99,7 @@ export function ExpressionChart({ const firstTimestamp = first(firstSeries.rows)!.timestamp; const lastTimestamp = last(firstSeries.rows)!.timestamp; - const metric: MetricsExplorerOptionsMetric = { - field: expression.metric, - aggregation: expression.aggType as MetricsExplorerAggregation, - color: Color.color0, - }; - - if (metric.aggregation === 'custom') { - metric.label = expression.label || CUSTOM_EQUATION; - } + const name = expression.label || CUSTOM_EQUATION; const dateFormatter = firstTimestamp == null || lastTimestamp == null @@ -130,13 +115,13 @@ export function ExpressionChart({ rows: firstSeries.rows.map((row) => { const newRow: MetricsExplorerRow = { ...row }; thresholds.forEach((thresholdValue, index) => { - newRow[getMetricId(metric, `threshold_${index}`)] = thresholdValue; + newRow[`metric_threshold_${index}`] = thresholdValue; }); return newRow; }), }; - const dataDomain = calculateDomain(series, [metric], false); + const dataDomain = calculateDomain(series); const domain = { max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, min: Math.min(dataDomain.min, first(thresholds) || dataDomain.min) * 0.9, // add 10% floor, @@ -155,7 +140,8 @@ export function ExpressionChart({ { { name: 'system.cpu.user.pct', type: 'test', - searchable: true, - aggregatable: true, - displayable: true, }, { name: 'system.load.1', type: 'test', - searchable: true, - aggregatable: true, - displayable: true, }, ]} remove={() => {}} @@ -61,15 +55,21 @@ describe('ExpressionRow', () => { } it('should display thresholds as a percentage for pct metrics', async () => { - const expression = { - metric: 'system.cpu.user.pct', + const expression: MetricExpression = { + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + field: 'system.cpu.user.pct', + }, + ], threshold: [0.5], timeSize: 1, timeUnit: 'm', - aggType: 'custom', }; - const { wrapper, update } = await setup(expression as MetricExpression); + const { wrapper, update } = await setup(expression); await update(); const [valueMatch] = wrapper @@ -82,12 +82,18 @@ describe('ExpressionRow', () => { it('should display thresholds as a decimal for all other metrics', async () => { const expression = { - metric: 'system.load.1', + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + field: 'system.load.1', + }, + ], threshold: [0.5], timeSize: 1, timeUnit: 'm', - aggType: 'custom', }; const { wrapper } = await setup(expression as MetricExpression); const [valueMatch] = diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index e508977658c0c..5521874bbb497 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { EuiButtonIcon, EuiFieldText, @@ -22,10 +23,10 @@ import { IErrorObject, ThresholdExpression, } from '@kbn/triggers-actions-ui-plugin/public'; -import { DataViewBase } from '@kbn/es-query'; +import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { debounce } from 'lodash'; import { Comparator } from '../../../../common/custom_threshold_rule/types'; -import { AGGREGATION_TYPES, DerivedIndexPattern, MetricExpression } from '../types'; +import { AGGREGATION_TYPES, MetricExpression } from '../types'; import { CustomEquationEditor } from './custom_equation'; import { CUSTOM_EQUATION, LABEL_HELP_MESSAGE, LABEL_LABEL } from '../i18n_strings'; import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert'; @@ -42,7 +43,7 @@ const customComparators = { }; interface ExpressionRowProps { - fields: DerivedIndexPattern['fields']; + fields: DataViewFieldBase[]; expressionId: number; expression: MetricExpression; errors: IErrorObject; @@ -74,9 +75,12 @@ export const ExpressionRow: React.FC = (props) => { canDelete, } = props; - const { metric, comparator = Comparator.GT, threshold = [] } = expression; + const { metrics, comparator = Comparator.GT, threshold = [] } = expression; - const isMetricPct = useMemo(() => Boolean(metric && metric.endsWith('.pct')), [metric]); + const isMetricPct = useMemo( + () => Boolean(metrics.length === 1 && metrics[0].field?.endsWith('.pct')), + [metrics] + ); const [label, setLabel] = useState(expression?.label || undefined); const updateComparator = useCallback( @@ -277,17 +281,6 @@ export const aggregationType: { [key: string]: AggregationType } = { value: AGGREGATION_TYPES.CARDINALITY, validNormalizedTypes: ['number', 'string', 'ip', 'date'], }, - rate: { - text: i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.rate', - { - defaultMessage: 'Rate', - } - ), - fieldRequired: false, - value: AGGREGATION_TYPES.RATE, - validNormalizedTypes: ['number'], - }, count: { text: i18n.translate( 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.count', @@ -310,28 +303,6 @@ export const aggregationType: { [key: string]: AggregationType } = { value: AGGREGATION_TYPES.SUM, validNormalizedTypes: ['number', 'histogram'], }, - p95: { - text: i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p95', - { - defaultMessage: '95th Percentile', - } - ), - fieldRequired: false, - value: AGGREGATION_TYPES.P95, - validNormalizedTypes: ['number', 'histogram'], - }, - p99: { - text: i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p99', - { - defaultMessage: '99th Percentile', - } - ), - fieldRequired: false, - value: AGGREGATION_TYPES.P99, - validNormalizedTypes: ['number', 'histogram'], - }, custom: { text: CUSTOM_EQUATION, fieldRequired: false, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx index a0ea640e19366..23870a92ad981 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx @@ -6,26 +6,27 @@ */ import { EuiComboBox } from '@elastic/eui'; +import { DataViewFieldBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; -import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; -import { DerivedIndexPattern } from '../types'; + +export type MetricsExplorerFields = Array; + +export type GroupBy = string | null | string[]; +export interface GroupByOptions { + groupBy: GroupBy; +} interface Props { - options: MetricsExplorerOptions; - onChange: (groupBy: string | null | string[]) => void; - fields: DerivedIndexPattern['fields']; + options: GroupByOptions; + onChange: (groupBy: GroupBy) => void; + fields: MetricsExplorerFields; errorOptions?: string[]; } -export function MetricsExplorerGroupBy({ - options, - onChange, - fields, - errorOptions, - ...rest -}: Props) { +export function GroupBy({ options, onChange, fields, errorOptions, ...rest }: Props) { + console.log('options:', options); const handleChange = useCallback( (selectedOptions: Array<{ label: string }>) => { const groupBy = selectedOptions.map((option) => option.label); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx index c0e35c85580bc..6ed310823863c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx @@ -16,19 +16,15 @@ import { } from '@elastic/charts'; import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { Color, colorTransformer } from '../../../../common/custom_threshold_rule/color_palette'; -import { - MetricsExplorerChartType, - MetricsExplorerOptionsMetric, -} from '../../../../common/custom_threshold_rule/types'; +import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; -import { getMetricId } from '../helpers/get_metric_id'; import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_time_zone_setting'; -import { createMetricLabel } from '../helpers/create_metric_label'; type NumberOrString = string | number; interface Props { - metric: MetricsExplorerOptionsMetric; + name: string; + color: Color; id: NumberOrString | NumberOrString[]; series: MetricsExplorerSeries; type: MetricsExplorerChartType; @@ -43,17 +39,15 @@ export function MetricExplorerSeriesChart(props: Props) { return ; } -export function MetricsExplorerAreaChart({ metric, id, series, type, stack, opacity }: Props) { +export function MetricsExplorerAreaChart({ name, color, id, series, type, stack, opacity }: Props) { const timezone = useKibanaTimeZoneSetting(); - const color = (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0); + const seriesColor = (color && colorTransformer(color)) || colorTransformer(Color.color0); const yAccessors = Array.isArray(id) - ? id.map((i) => getMetricId(metric, i)).slice(id.length - 1, id.length) - : [getMetricId(metric, id)]; + ? id.map((i) => `metric_${i}`).slice(id.length - 1, id.length) + : [`metric_${id}`]; const y0Accessors = - Array.isArray(id) && id.length > 1 - ? id.map((i) => getMetricId(metric, i)).slice(0, 1) - : undefined; + Array.isArray(id) && id.length > 1 ? id.map((i) => `metric_${i}`).slice(0, 1) : undefined; const chartId = `series-${series.id}-${yAccessors.join('-')}`; const seriesAreaStyle: RecursivePartial = { @@ -71,7 +65,7 @@ export function MetricsExplorerAreaChart({ metric, id, series, type, stack, opac ); } -export function MetricsExplorerBarChart({ metric, id, series, stack }: Props) { +export function MetricsExplorerBarChart({ name, color, id, series, stack }: Props) { const timezone = useKibanaTimeZoneSetting(); - const color = (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0); + const seriesColor = (color && colorTransformer(color)) || colorTransformer(Color.color0); const yAccessors = Array.isArray(id) - ? id.map((i) => getMetricId(metric, i)).slice(id.length - 1, id.length) - : [getMetricId(metric, id)]; + ? id.map((i) => `metric_${i}`).slice(id.length - 1, id.length) + : [`metric_${id}`]; const chartId = `series-${series.id}-${yAccessors.join('-')}`; const seriesBarStyle: RecursivePartial = { rectBorder: { - stroke: color, + stroke: seriesColor, strokeWidth: 1, visible: true, }, @@ -109,7 +103,7 @@ export function MetricsExplorerBarChart({ metric, id, series, stack }: Props) { ); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx index 143229f55062c..f360b415fa1be 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -10,7 +10,10 @@ import { buildEsQuery, fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { isEmpty } from 'lodash'; -import { Comparator, MetricExpressionParams } from '../../../../common/custom_threshold_rule/types'; +import { + Comparator, + CustomMetricExpressionParams, +} from '../../../../common/custom_threshold_rule/types'; export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; @@ -18,7 +21,7 @@ export function validateCustomThreshold({ criteria, searchConfiguration, }: { - criteria: MetricExpressionParams[]; + criteria: CustomMetricExpressionParams[]; searchConfiguration: SerializedSearchSourceFields; }): ValidationResult { const validationResult = { errors: {} }; @@ -35,7 +38,6 @@ export function validateCustomThreshold({ threshold0: string[]; threshold1: string[]; }; - metric: string[]; metricsError?: string; metrics: Record; equation?: string; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx index f11fc9db40b22..502f6a0ecd743 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx @@ -13,7 +13,6 @@ import { queryClient } from '@kbn/osquery-plugin/public/query_client'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { Aggregators, Comparator } from '../../../common/custom_threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../common/custom_threshold_rule/metrics_explorer'; import { useKibana } from '../../utils/kibana_react'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; import Expressions from './custom_threshold_rule_expression'; @@ -36,11 +35,7 @@ describe('Expression', () => { mockKibana(); }); - async function setup(currentOptions: { - metrics?: MetricsExplorerMetric[]; - filterQuery?: string; - groupBy?: string; - }) { + async function setup() { const ruleParams = { criteria: [], groupBy: undefined, @@ -64,7 +59,6 @@ describe('Expression', () => { setRuleParams={(key, value) => Reflect.set(ruleParams, key, value)} setRuleProperty={() => {}} metadata={{ - currentOptions, adHocDataViewList: [], }} dataViews={dataViewMock} @@ -84,41 +78,8 @@ describe('Expression', () => { return { wrapper, update, ruleParams }; } - it('should prefill the alert using the context metadata', async () => { - const currentOptions = { - groupBy: 'host.hostname', - filterQuery: 'foo', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; - const { ruleParams } = await setup(currentOptions); - expect(ruleParams.groupBy).toBe('host.hostname'); - expect(ruleParams.searchConfiguration.query.query).toBe('foo'); - expect(ruleParams.criteria).toEqual([ - { - metric: 'system.load.1', - comparator: Comparator.GT, - threshold: [], - timeSize: 1, - timeUnit: 'm', - aggType: 'avg', - }, - { - metric: 'system.cpu.user.pct', - comparator: Comparator.GT, - threshold: [], - timeSize: 1, - timeUnit: 'm', - aggType: 'cardinality', - }, - ]); - }); - it('should use default metrics', async () => { - const currentOptions = {}; - const { ruleParams } = await setup(currentOptions); + const { ruleParams } = await setup(); expect(ruleParams.criteria).toEqual([ { metrics: [ @@ -137,13 +98,6 @@ describe('Expression', () => { }); it('should show an error message when searchSource throws an error', async () => { - const currentOptions = { - groupBy: 'host.hostname', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; const errorMessage = 'Error in searchSource create'; const kibanaMock = kibanaStartMock.startContract(); useKibanaMock.mockReturnValue({ @@ -169,20 +123,13 @@ describe('Expression', () => { }, }, }); - const { wrapper } = await setup(currentOptions); + const { wrapper } = await setup(); expect(wrapper.find(`[data-test-subj="thresholdRuleExpressionError"]`).first().text()).toBe( errorMessage ); }); it('should show no timestamp error when selected data view does not have a timeField', async () => { - const currentOptions = { - groupBy: 'host.hostname', - metrics: [ - { aggregation: 'avg', field: 'system.load.1' }, - { aggregation: 'cardinality', field: 'system.cpu.user.pct' }, - ] as MetricsExplorerMetric[], - }; const mockedIndex = { id: 'c34a7c79-a88b-4b4a-ad19-72f6d24104e4', title: 'metrics-fake_hosts', @@ -234,7 +181,7 @@ describe('Expression', () => { }, }, }); - const { wrapper } = await setup(currentOptions); + const { wrapper } = await setup(); expect( wrapper.find(`[data-test-subj="thresholdRuleDataViewErrorNoTimestamp"]`).first().text() ).toBe( diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx index 26de96e3bd835..3d737bdd3b861 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -41,8 +41,7 @@ import { TimeUnitChar } from '../../../common/utils/formatters/duration'; import { AlertContextMeta, AlertParams, MetricExpression } from './types'; import { ExpressionChart } from './components/expression_chart'; import { ExpressionRow } from './components/expression_row'; -import { MetricsExplorerGroupBy } from './components/group_by'; -import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; +import { MetricsExplorerFields, GroupBy } from './components/group_by'; const FILTER_TYPING_DEBOUNCE_MS = 500; @@ -152,15 +151,7 @@ export default function Expressions(props: Props) { setTimeSize(ruleParams.criteria[0].timeSize); setTimeUnit(ruleParams.criteria[0].timeUnit); } else { - preFillAlertCriteria(); - } - - if (!ruleParams.filterQuery) { - preFillAlertFilter(); - } - - if (!ruleParams.groupBy) { - preFillAlertGroupBy(); + setRuleParams('criteria', [defaultExpression]); } if (typeof ruleParams.alertOnNoData === 'undefined') { @@ -171,17 +162,6 @@ export default function Expressions(props: Props) { } }, [metadata]); // eslint-disable-line react-hooks/exhaustive-deps - const options = useMemo(() => { - if (metadata?.currentOptions?.metrics) { - return metadata.currentOptions as MetricsExplorerOptions; - } else { - return { - metrics: [], - aggregation: 'count', - }; - } - }, [metadata]); - const onSelectDataView = useCallback( (newDataView: DataView) => { const ruleCriteria = (ruleParams.criteria ? ruleParams.criteria.slice() : []).map( @@ -282,67 +262,11 @@ export default function Expressions(props: Props) { [ruleParams.criteria, setRuleParams] ); - const preFillAlertCriteria = useCallback(() => { - const md = metadata; - if (md?.currentOptions?.metrics?.length) { - setRuleParams( - 'criteria', - md.currentOptions.metrics.map((metric) => ({ - metric: metric.field, - comparator: Comparator.GT, - threshold: [], - timeSize, - timeUnit, - aggType: metric.aggregation, - })) as AlertParams['criteria'] - ); - } else { - setRuleParams('criteria', [defaultExpression]); - } - }, [metadata, setRuleParams, timeSize, timeUnit]); - - const preFillAlertFilter = useCallback(() => { - const md = metadata; - if (md && md.currentOptions?.filterQuery) { - setRuleParams('searchConfiguration', { - ...ruleParams.searchConfiguration, - query: { - query: md.currentOptions.filterQuery, - language: 'kuery', - }, - }); - } else if (md && md.currentOptions?.groupBy && md.series) { - const { groupBy } = md.currentOptions; - const query = Array.isArray(groupBy) - ? groupBy.map((field, index) => `${field}: "${md.series?.keys?.[index]}"`).join(' and ') - : `${groupBy}: "${md.series.id}"`; - setRuleParams('searchConfiguration', { - ...ruleParams.searchConfiguration, - query: { - query, - language: 'kuery', - }, - }); - } - }, [metadata, setRuleParams, ruleParams.searchConfiguration]); - - const preFillAlertGroupBy = useCallback(() => { - const md = metadata; - if (md && md.currentOptions?.groupBy && !md.series) { - setRuleParams('groupBy', md.currentOptions.groupBy); - } - }, [metadata, setRuleParams]); - const hasGroupBy = useMemo( () => ruleParams.groupBy && ruleParams.groupBy.length > 0, [ruleParams.groupBy] ); - const disableNoData = useMemo( - () => ruleParams.criteria?.every((c) => c.aggType === Aggregators.COUNT), - [ruleParams.criteria] - ); - // Test to see if any of the group fields in groupBy are already filtered down to a single // group by the filterQuery. If this is the case, then a groupBy is unnecessary, as it would only // ever produce one group instance @@ -477,7 +401,7 @@ export default function Expressions(props: Props) { )} 1) || false} - fields={derivedIndexPattern.fields as any} + fields={derivedIndexPattern.fields} remove={removeExpression} addExpression={addExpression} key={idx} // idx's don't usually make good key's but here the index has semantic meaning @@ -543,12 +467,11 @@ export default function Expressions(props: Props) { fullWidth display="rowCompressed" > - @@ -591,22 +514,19 @@ export default function Expressions(props: Props) { } )}{' '} } - disabled={disableNoData || !hasGroupBy} + disabled={!hasGroupBy} checked={Boolean(hasGroupBy && ruleParams.alertOnGroupDisappear)} onChange={(e) => setRuleParams('alertOnGroupDisappear', e.target.checked)} /> @@ -614,10 +534,3 @@ export default function Expressions(props: Props) { ); } - -const docCountNoDataDisabledHelpText = i18n.translate( - 'xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText', - { - defaultMessage: '[This setting is not applicable to the Document Count aggregator.]', - } -); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts index 16d0d54d825c1..1794caa599005 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { min, max, sum, isNumber } from 'lodash'; +import { min, max, isNumber } from 'lodash'; import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; -import { getMetricId } from './get_metric_id'; const getMin = (values: Array) => { const minValue = min(values); @@ -20,24 +18,7 @@ const getMax = (values: Array) => { return isNumber(maxValue) && Number.isFinite(maxValue) ? maxValue : undefined; }; -export const calculateDomain = ( - series: MetricsExplorerSeries, - metrics: MetricsExplorerOptionsMetric[], - stacked = false -): { min: number; max: number } => { - const values = series.rows - .reduce((acc, row) => { - const rowValues = metrics - .map((m, index) => { - return (row[getMetricId(m, index)] as number) || null; - }) - .filter((v) => isNumber(v)); - const minValue = getMin(rowValues); - // For stacked domains we want to add 10% head room so the charts have - // enough room to draw the 2 pixel line as well. - const maxValue = stacked ? sum(rowValues) * 1.1 : getMax(rowValues); - return acc.concat([minValue || null, maxValue || null]); - }, [] as Array) - .filter((v) => isNumber(v)); +export const calculateDomain = (series: MetricsExplorerSeries): { min: number; max: number } => { + const values = series.rows.map((row) => row.metric_0 as number | null).filter((v) => isNumber(v)); return { min: getMin(values) || 0, max: getMax(values) || 0 }; }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts index 1423413e98388..97087797094d0 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts @@ -6,21 +6,15 @@ */ import numeral from '@elastic/numeral'; -import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; +import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; import { createFormatter } from '../../../../common/custom_threshold_rule/formatters'; -import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { metricToFormat } from './metric_to_format'; -export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => { - if (metric?.aggregation === 'custom') { - return (input: number) => numeral(input).format('0.[0000]'); - } - if (metric && metric.field) { - const format = metricToFormat(metric); - if (format === InfraFormatterType.bits && metric.aggregation === 'rate') { - return createFormatter(InfraFormatterType.bits, '{{value}}/s'); - } +export const createFormatterForMetric = (metrics: CustomThresholdExpressionMetric[]) => { + if (metrics.length === 1) { + const format = metricToFormat(metrics[0]); return createFormatter(format); } - return createFormatter(InfraFormatterType.number); + + return (input: number) => numeral(input).format('0.[0000]'); }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts index 0da0e765d724a..000058abc2f9b 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts @@ -5,38 +5,36 @@ * 2.0. */ -import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { + Aggregators, + CustomThresholdExpressionMetric, +} from '../../../../common/custom_threshold_rule/types'; import { createFormatterForMetric } from './create_formatter_for_metric'; describe('createFormatterForMetric()', () => { it('should just work for count', () => { - const metric: MetricsExplorerMetric = { aggregation: 'count' }; + const metric: CustomThresholdExpressionMetric[] = [{ name: 'A', aggType: Aggregators.COUNT }]; const format = createFormatterForMetric(metric); expect(format(1291929)).toBe('1,291,929'); }); it('should just work for numerics', () => { - const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.load.1' }; + const metric: CustomThresholdExpressionMetric[] = [ + { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.load.1' }, + ]; const format = createFormatterForMetric(metric); expect(format(1000.2)).toBe('1,000.2'); }); it('should just work for percents', () => { - const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.cpu.total.pct' }; + const metric: CustomThresholdExpressionMetric[] = [ + { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.total.pct' }, + ]; const format = createFormatterForMetric(metric); expect(format(0.349)).toBe('34.9%'); }); - it('should just work for rates', () => { - const metric: MetricsExplorerMetric = { - aggregation: 'rate', - field: 'host.network.egress.bytes', - }; - const format = createFormatterForMetric(metric); - expect(format(103929292)).toBe('831.4 Mbit/s'); - }); it('should just work for bytes', () => { - const metric: MetricsExplorerMetric = { - aggregation: 'avg', - field: 'host.network.egress.bytes', - }; + const metric: CustomThresholdExpressionMetric[] = [ + { name: 'A', aggType: Aggregators.AVERAGE, field: 'host.network.egress.bytes' }, + ]; const format = createFormatterForMetric(metric); expect(format(103929292)).toBe('103.9 MB'); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts deleted file mode 100644 index 70c4959aafc19..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts +++ /dev/null @@ -1,20 +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 { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { createMetricLabel } from './create_metric_label'; - -describe('createMetricLabel()', () => { - it('should work with metrics with fields', () => { - const metric: MetricsExplorerMetric = { aggregation: 'avg', field: 'system.load.1' }; - expect(createMetricLabel(metric)).toBe('avg(system.load.1)'); - }); - it('should work with document count', () => { - const metric: MetricsExplorerMetric = { aggregation: 'count' }; - expect(createMetricLabel(metric)).toBe('count()'); - }); -}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts deleted file mode 100644 index 1b3496c07cd43..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts +++ /dev/null @@ -1,15 +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 { MetricsExplorerOptionsMetric } from '../hooks/use_metrics_explorer_options'; - -export const createMetricLabel = (metric: MetricsExplorerOptionsMetric) => { - if (metric.label) { - return metric.label; - } - return `${metric.aggregation}(${metric.field || ''})`; -}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts deleted file mode 100644 index a60af79ac6e2d..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts +++ /dev/null @@ -1,12 +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 { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; - -export const getMetricId = (metric: MetricsExplorerOptionsMetric, index: string | number) => { - return `metric_${index}`; -}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts index 2a7d28b72b7c7..67710c49c2acd 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts @@ -6,18 +6,17 @@ */ import { last } from 'lodash'; -import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { + CustomThresholdExpressionMetric, + InfraFormatterType, +} from '../../../../common/custom_threshold_rule/types'; -export const metricToFormat = (metric?: MetricsExplorerMetric) => { +export const metricToFormat = (metric?: CustomThresholdExpressionMetric) => { if (metric && metric.field) { const suffix = last(metric.field.split(/\./)); if (suffix === 'pct') { return InfraFormatterType.percent; } - if (suffix === 'bytes' && metric.aggregation === 'rate') { - return InfraFormatterType.bits; - } if (suffix === 'bytes') { return InfraFormatterType.bytes; } diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts deleted file mode 100644 index 82a46eac7d01a..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts +++ /dev/null @@ -1,16 +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 createContainer from 'constate'; -import { useCustomThresholdAlertPrefill } from '../hooks/use_metric_threshold_alert_prefill'; - -const useAlertPrefill = () => { - const customThresholdPrefill = useCustomThresholdAlertPrefill(); - return { customThresholdPrefill }; -}; - -export const [AlertPrefillProvider, useAlertPrefillContext] = createContainer(useAlertPrefill); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts similarity index 73% rename from x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts index f6ef1a8282224..589ccbf5b798c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts @@ -10,18 +10,13 @@ import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; import { MetricExplorerCustomMetricAggregations } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; -import { MetricExpression, TimeRange } from '../types'; -import { useMetricsExplorerData } from './use_metrics_explorer_data'; - -import { - MetricsExplorerOptions, - MetricsExplorerTimestampsRT, -} from './use_metrics_explorer_options'; +import { ExpressionOptions, ExpressionTimestampsRT, MetricExpression, TimeRange } from '../types'; +import { useExpressionData } from './use_expression_data'; const DEFAULT_TIME_RANGE = {}; const DEFAULT_TIMESTAMP = '@timestamp'; -export const useMetricsExplorerChartData = ( +export const useExpressionChartData = ( expression: MetricExpression, derivedIndexPattern: DataViewBase, filterQuery?: string, @@ -31,7 +26,7 @@ export const useMetricsExplorerChartData = ( ) => { const { timeSize, timeUnit } = expression || { timeSize: 1, timeUnit: 'm' }; - const options: MetricsExplorerOptions = useMemo( + const options: ExpressionOptions = useMemo( () => ({ limit: 1, forceInterval: true, @@ -39,14 +34,14 @@ export const useMetricsExplorerChartData = ( groupBy, filterQuery, metrics: [ - expression.aggType === 'custom' - ? { - aggregation: 'custom', - custom_metrics: - expression?.metrics?.map(mapCustomThresholdMetricToMetricsExplorerMetric) ?? [], - equation: expression.equation, - } - : { field: expression.metric, aggregation: expression.aggType }, + { + aggregation: 'custom', + // Infra API expects this field to be custom_metrics + // since the same field is used in the metric threshold rule + custom_metrics: + expression?.metrics?.map(mapCustomThresholdMetricToMetricsExplorerMetric) ?? [], + equation: expression.equation, + }, ], aggregation: expression.aggType || 'avg', }), @@ -54,14 +49,13 @@ export const useMetricsExplorerChartData = ( [ expression.aggType, expression.equation, - expression.metric, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(expression.metrics), filterQuery, groupBy, ] ); - const timestamps: MetricsExplorerTimestampsRT = useMemo(() => { + const timestamps: ExpressionTimestampsRT = useMemo(() => { const from = timeRange.from ?? `now-${(timeSize || 1) * 20}${timeUnit}`; const to = timeRange.to ?? 'now'; const fromTimestamp = DateMath.parse(from)!.valueOf(); @@ -74,7 +68,7 @@ export const useMetricsExplorerChartData = ( }; }, [timeRange.from, timeRange.to, timeSize, timeUnit, timeFieldName]); - return useMetricsExplorerData(options, derivedIndexPattern, timestamps); + return useExpressionData(options, derivedIndexPattern, timestamps); }; const mapCustomThresholdMetricToMetricsExplorerMetric = ( diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts similarity index 87% rename from x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts index 59857990b906e..ae077e90d7dd0 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts @@ -13,18 +13,14 @@ import { MetricsExplorerResponse, metricsExplorerResponseRT, } from '../../../../common/custom_threshold_rule/metrics_explorer'; - -import { - MetricsExplorerOptions, - MetricsExplorerTimestampsRT, -} from './use_metrics_explorer_options'; import { convertKueryToElasticSearchQuery } from '../helpers/kuery'; import { decodeOrThrow } from '../helpers/runtime_types'; +import { ExpressionOptions, ExpressionTimestampsRT } from '../types'; -export function useMetricsExplorerData( - options: MetricsExplorerOptions, +export function useExpressionData( + options: ExpressionOptions, derivedIndexPattern: DataViewBase, - { fromTimestamp, toTimestamp, interval, timeFieldName }: MetricsExplorerTimestampsRT, + { fromTimestamp, toTimestamp, interval, timeFieldName }: ExpressionTimestampsRT, enabled = true ) { const { http } = useKibana().services; @@ -51,7 +47,7 @@ export function useMetricsExplorerData( body: JSON.stringify({ forceInterval: options.forceInterval, dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true, - metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] : options.metrics, + metrics: options.metrics, groupBy: options.groupBy, afterKey, limit: options.limit, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts index 5270f13dce1d8..c54e7a075956a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts @@ -7,19 +7,27 @@ import { isEqual } from 'lodash'; import { useState } from 'react'; -import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { + Aggregators, + CustomThresholdExpressionMetric, +} from '../../../../common/custom_threshold_rule/types'; export interface CustomThresholdPrefillOptions { - groupBy: string | string[] | undefined; + groupBy?: string[]; filterQuery: string | undefined; - metrics: MetricsExplorerMetric[]; + metrics: CustomThresholdExpressionMetric[]; } export const useCustomThresholdAlertPrefill = () => { const [prefillOptionsState, setPrefillOptionsState] = useState({ groupBy: undefined, filterQuery: undefined, - metrics: [], + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + }, + ], }); const { groupBy, filterQuery, metrics } = prefillOptionsState; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx index b0d846ce9b93d..cf08b1ac27743 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx @@ -7,15 +7,11 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { useMetricsExplorerData } from './use_metrics_explorer_data'; - import { renderHook } from '@testing-library/react-hooks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { - MetricsExplorerOptions, - MetricsExplorerTimestampsRT, -} from './use_metrics_explorer_options'; +import { ExpressionOptions, ExpressionTimestampsRT } from '../types'; +import { useExpressionData } from './use_expression_data'; import { DataViewBase } from '@kbn/es-query'; import { createSeries, @@ -51,10 +47,10 @@ const renderUseMetricsExplorerDataHook = () => { }; return renderHook( (props: { - options: MetricsExplorerOptions; + options: ExpressionOptions; derivedIndexPattern: DataViewBase; - timestamps: MetricsExplorerTimestampsRT; - }) => useMetricsExplorerData(props.options, props.derivedIndexPattern, props.timestamps), + timestamps: ExpressionTimestampsRT; + }) => useExpressionData(props.options, props.derivedIndexPattern, props.timestamps), { initialProps: { options, @@ -72,7 +68,7 @@ jest.mock('../helpers/kuery', () => { }; }); -describe('useMetricsExplorerData Hook', () => { +describe('useExpressionData Hook', () => { afterEach(() => { queryClient.clear(); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx deleted file mode 100644 index 23d97d0d1cae8..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx +++ /dev/null @@ -1,127 +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 { renderHook, act } from '@testing-library/react-hooks'; -import { - useMetricsExplorerOptions, - MetricsExplorerOptions, - MetricsExplorerTimeOptions, - DEFAULT_OPTIONS, - DEFAULT_TIMERANGE, -} from './use_metrics_explorer_options'; - -let PREFILL: Record = {}; -jest.mock('../helpers/use_alert_prefill', () => ({ - useAlertPrefillContext: () => ({ - customThresholdPrefill: { - setPrefillOptions(opts: Record) { - PREFILL = opts; - }, - }, - }), -})); - -jest.mock('./use_kibana_timefilter_time', () => ({ - useKibanaTimefilterTime: (defaults: { from: string; to: string }) => [() => defaults], - useSyncKibanaTimeFilterTime: () => [() => {}], -})); - -const renderUseMetricsExplorerOptionsHook = () => renderHook(() => useMetricsExplorerOptions()); - -interface LocalStore { - [key: string]: string; -} - -interface LocalStorage { - getItem: (key: string) => string | null; - setItem: (key: string, value: string) => void; -} - -const STORE: LocalStore = {}; -const localStorageMock: LocalStorage = { - getItem: (key: string) => { - return STORE[key] || null; - }, - setItem: (key: string, value: any) => { - STORE[key] = value.toString(); - }, -}; - -Object.defineProperty(window, 'localStorage', { - value: localStorageMock, -}); - -describe('useMetricExplorerOptions', () => { - beforeEach(() => { - delete STORE.MetricsExplorerOptions; - delete STORE.MetricsExplorerTimeRange; - PREFILL = {}; - }); - - it('should just work', () => { - const { result } = renderUseMetricsExplorerOptionsHook(); - expect(result.current.options).toEqual(DEFAULT_OPTIONS); - expect(result.current.timeRange).toEqual(DEFAULT_TIMERANGE); - expect(result.current.isAutoReloading).toEqual(false); - expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(DEFAULT_OPTIONS)); - }); - - it('should change the store when options update', () => { - const { result, rerender } = renderUseMetricsExplorerOptionsHook(); - const newOptions: MetricsExplorerOptions = { - ...DEFAULT_OPTIONS, - metrics: [{ aggregation: 'count' }], - }; - act(() => { - result.current.setOptions(newOptions); - }); - rerender(); - expect(result.current.options).toEqual(newOptions); - expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(newOptions)); - }); - - it('should change the store when timerange update', () => { - const { result, rerender } = renderUseMetricsExplorerOptionsHook(); - const newTimeRange: MetricsExplorerTimeOptions = { - ...DEFAULT_TIMERANGE, - from: 'now-15m', - }; - act(() => { - result.current.setTimeRange(newTimeRange); - }); - rerender(); - expect(result.current.timeRange).toEqual(newTimeRange); - }); - - it('should load from store when available', () => { - const newOptions: MetricsExplorerOptions = { - ...DEFAULT_OPTIONS, - metrics: [{ aggregation: 'avg', field: 'system.load.1' }], - }; - STORE.MetricsExplorerOptions = JSON.stringify(newOptions); - const { result } = renderUseMetricsExplorerOptionsHook(); - expect(result.current.options).toEqual(newOptions); - }); - - it('should sync the options to the threshold alert preview context', () => { - const { result, rerender } = renderUseMetricsExplorerOptionsHook(); - - const newOptions: MetricsExplorerOptions = { - ...DEFAULT_OPTIONS, - metrics: [{ aggregation: 'count' }], - filterQuery: 'foo', - groupBy: 'host.hostname', - }; - act(() => { - result.current.setOptions(newOptions); - }); - rerender(); - expect(PREFILL.metrics).toEqual(newOptions.metrics); - expect(PREFILL.groupBy).toEqual(newOptions.groupBy); - expect(PREFILL.filterQuery).toEqual(newOptions.filterQuery); - }); -}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts deleted file mode 100644 index f03ea875e5957..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts +++ /dev/null @@ -1,236 +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 DateMath from '@kbn/datemath'; -import * as t from 'io-ts'; -import { values } from 'lodash'; -import createContainer from 'constate'; -import type { TimeRange } from '@kbn/es-query'; -import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; - -import { metricsExplorerMetricRT } from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { Color } from '../../../../common/custom_threshold_rule/color_palette'; - -import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; -import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime } from './use_kibana_timefilter_time'; - -const metricsExplorerOptionsMetricRT = t.intersection([ - metricsExplorerMetricRT, - t.partial({ - rate: t.boolean, - color: t.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), - label: t.string, - }), -]); - -export type MetricsExplorerOptionsMetric = t.TypeOf; - -export enum MetricsExplorerChartType { - line = 'line', - area = 'area', - bar = 'bar', -} - -export enum MetricsExplorerYAxisMode { - fromZero = 'fromZero', - auto = 'auto', -} - -export const metricsExplorerChartOptionsRT = t.type({ - yAxisMode: t.keyof( - Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< - MetricsExplorerYAxisMode, - null - > - ), - type: t.keyof( - Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record< - MetricsExplorerChartType, - null - > - ), - stack: t.boolean, -}); - -export type MetricsExplorerChartOptions = t.TypeOf; - -const metricExplorerOptionsRequiredRT = t.type({ - aggregation: t.string, - metrics: t.array(metricsExplorerOptionsMetricRT), -}); - -const metricExplorerOptionsOptionalRT = t.partial({ - limit: t.number, - groupBy: t.union([t.string, t.array(t.string)]), - filterQuery: t.string, - source: t.string, - forceInterval: t.boolean, - dropLastBucket: t.boolean, -}); -export const metricExplorerOptionsRT = t.intersection([ - metricExplorerOptionsRequiredRT, - metricExplorerOptionsOptionalRT, -]); - -export type MetricsExplorerOptions = t.TypeOf; - -export const metricsExplorerTimestampsRT = t.type({ - fromTimestamp: t.number, - toTimestamp: t.number, - interval: t.string, - timeFieldName: t.string, -}); -export type MetricsExplorerTimestampsRT = t.TypeOf; - -export const metricsExplorerTimeOptionsRT = t.type({ - from: t.string, - to: t.string, - interval: t.string, -}); -export type MetricsExplorerTimeOptions = t.TypeOf; - -export const DEFAULT_TIMERANGE: MetricsExplorerTimeOptions = { - from: 'now-1h', - to: 'now', - interval: '>=10s', -}; - -export const DEFAULT_CHART_OPTIONS: MetricsExplorerChartOptions = { - type: MetricsExplorerChartType.line, - yAxisMode: MetricsExplorerYAxisMode.fromZero, - stack: false, -}; - -export const DEFAULT_METRICS: MetricsExplorerOptionsMetric[] = [ - { - aggregation: 'avg', - field: 'system.cpu.total.norm.pct', - color: Color.color0, - }, - { - aggregation: 'avg', - field: 'kubernetes.pod.cpu.usage.node.pct', - color: Color.color1, - }, - { - aggregation: 'avg', - field: 'docker.cpu.total.pct', - color: Color.color2, - }, -]; - -export const DEFAULT_OPTIONS: MetricsExplorerOptions = { - aggregation: 'avg', - metrics: DEFAULT_METRICS, - source: 'default', -}; - -export const DEFAULT_METRICS_EXPLORER_VIEW_STATE = { - options: DEFAULT_OPTIONS, - chartOptions: DEFAULT_CHART_OPTIONS, - currentTimerange: DEFAULT_TIMERANGE, -}; - -function parseJsonOrDefault(value: string | null, defaultValue: Obj): Obj { - if (!value) { - return defaultValue; - } - try { - return JSON.parse(value) as Obj; - } catch (e) { - return defaultValue; - } -} - -function useStateWithLocalStorage( - key: string, - defaultState: State -): [State, Dispatch>] { - const storageState = localStorage.getItem(key); - const [state, setState] = useState(parseJsonOrDefault(storageState, defaultState)); - useEffect(() => { - localStorage.setItem(key, JSON.stringify(state)); - }, [key, state]); - return [state, setState]; -} - -const getDefaultTimeRange = ({ from, to }: TimeRange) => { - const fromTimestamp = DateMath.parse(from)!.valueOf(); - const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf(); - return { - fromTimestamp, - toTimestamp, - interval: DEFAULT_TIMERANGE.interval, - }; -}; - -export const useMetricsExplorerOptions = () => { - const TIME_DEFAULTS = { from: 'now-1h', to: 'now' }; - const [getTime] = useKibanaTimefilterTime(TIME_DEFAULTS); - const { from, to } = getTime(); - - const [options, setOptions] = useStateWithLocalStorage( - 'MetricsExplorerOptions', - DEFAULT_OPTIONS - ); - const [timeRange, setTimeRange] = useState({ - from, - to, - interval: DEFAULT_TIMERANGE.interval, - }); - const [timestamps, setTimestamps] = useState({ - ...getDefaultTimeRange({ from, to }), - timeFieldName: '@timestamp', - }); - - useSyncKibanaTimeFilterTime(TIME_DEFAULTS, { - from: timeRange.from, - to: timeRange.to, - }); - - const [chartOptions, setChartOptions] = useStateWithLocalStorage( - 'MetricsExplorerChartOptions', - DEFAULT_CHART_OPTIONS - ); - const [isAutoReloading, setAutoReloading] = useState(false); - - const { customThresholdPrefill } = useAlertPrefillContext(); - // For Jest compatibility; including customThresholdPrefill as a dep in useEffect causes an - // infinite loop in test environment - const prefillContext = useMemo(() => customThresholdPrefill, [customThresholdPrefill]); - - useEffect(() => { - if (prefillContext) { - const { setPrefillOptions } = prefillContext; - const { metrics, groupBy, filterQuery } = options; - - setPrefillOptions({ metrics, groupBy, filterQuery }); - } - }, [options, prefillContext]); - - return { - defaultViewState: { - options: DEFAULT_OPTIONS, - chartOptions: DEFAULT_CHART_OPTIONS, - currentTimerange: timeRange, - }, - options, - chartOptions, - setChartOptions, - timeRange, - isAutoReloading, - setOptions, - setTimeRange, - startAutoReload: () => setAutoReloading(true), - stopAutoReload: () => setAutoReloading(false), - timestamps, - setTimestamps, - }; -}; - -export const [MetricsExplorerOptionsContainer, useMetricsExplorerOptionsContainerContext] = - createContainer(useMetricsExplorerOptions); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts deleted file mode 100644 index f6b20963ccb00..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts +++ /dev/null @@ -1,51 +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 { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; -import { MetricExpression } from '../types'; -import { generateUniqueKey } from './generate_unique_key'; - -describe('generateUniqueKey', () => { - const mockedCriteria: Array<[MetricExpression, string]> = [ - [ - { - aggType: Aggregators.COUNT, - comparator: Comparator.LT, - threshold: [2000, 5000], - timeSize: 15, - timeUnit: 'm', - }, - 'count<2000,5000', - ], - [ - { - aggType: Aggregators.CUSTOM, - comparator: Comparator.GT_OR_EQ, - threshold: [30], - timeSize: 15, - timeUnit: 'm', - }, - 'custom>=30', - ], - [ - { - aggType: Aggregators.AVERAGE, - comparator: Comparator.LT_OR_EQ, - threshold: [500], - timeSize: 15, - timeUnit: 'm', - metric: 'metric', - }, - 'avg(metric)<=500', - ], - ]; - it.each(mockedCriteria)('unique key of %p is %s', (input, output) => { - const uniqueKey = generateUniqueKey(input); - - expect(uniqueKey).toBe(output); - }); -}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts deleted file mode 100644 index ec83311055a08..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts +++ /dev/null @@ -1,14 +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 { MetricExpression } from '../types'; - -export const generateUniqueKey = (criterion: MetricExpression) => { - const metric = criterion.metric ? `(${criterion.metric})` : ''; - - return criterion.aggType + metric + criterion.comparator + criterion.threshold.join(','); -}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts deleted file mode 100644 index 116fd86cefbfe..0000000000000 --- a/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts +++ /dev/null @@ -1,32 +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 { first } from 'lodash'; -import { MetricsExplorerResponse } from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { CustomThresholdAlertParams, ExpressionChartSeries } from '../types'; - -export const transformMetricsExplorerData = ( - params: CustomThresholdAlertParams, - data: MetricsExplorerResponse | null -) => { - const { criteria } = params; - const firstSeries = first(data?.series); - if (criteria && firstSeries) { - const series = firstSeries.rows.reduce((acc, row) => { - const { timestamp } = row; - criteria.forEach((item, index) => { - if (!acc[index]) { - acc[index] = []; - } - const value = (row[`metric_${index}`] as number) || 0; - acc[index].push({ timestamp, value }); - }); - return acc; - }, [] as ExpressionChartSeries); - return { id: firstSeries.id, series }; - } -}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 9e77d916b9669..4790a03a8135f 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -59,29 +59,47 @@ export const buildCustomThresholdRule = ( params: { criteria: [ { - aggType: Aggregators.COUNT, + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + }, + ], threshold: [2000], timeSize: 15, timeUnit: 'm', }, { - aggType: Aggregators.MAX, + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'B', + aggType: Aggregators.MAX, + field: 'system.cpu.user.pct', + }, + ], threshold: [4], timeSize: 15, timeUnit: 'm', - metric: 'system.cpu.user.pct', warningComparator: Comparator.GT, warningThreshold: [2.2], }, { - aggType: Aggregators.MIN, + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'C', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + ], threshold: [0.8], timeSize: 15, timeUnit: 'm', - metric: 'system.memory.used.pct', }, ], searchConfiguration: { @@ -136,20 +154,32 @@ export const buildCustomThresholdAlert = ( 'kibana.alert.rule.parameters': { criteria: [ { - aggType: Aggregators.AVERAGE, + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'A', + aggType: Aggregators.AVERAGE, + field: 'system.cpu.user.pct', + }, + ], threshold: [2000], timeSize: 15, timeUnit: 'm', - metric: 'system.cpu.user.pct', }, { - aggType: Aggregators.MAX, + aggType: Aggregators.CUSTOM, comparator: Comparator.GT, + metrics: [ + { + name: 'B', + aggType: Aggregators.MAX, + metric: 'system.cpu.user.pct', + }, + ], threshold: [4], timeSize: 15, timeUnit: 'm', - metric: 'system.cpu.user.pct', warningComparator: Comparator.GT, warningThreshold: [2.2], }, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts index 13a6e58aded98..08f133d07734d 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -24,32 +24,19 @@ import { import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { MetricsExplorerSeries } from '../../../common/custom_threshold_rule/metrics_explorer'; import { CustomMetricExpressionParams, - MetricExpressionParams, - MetricsSourceStatus, - NonCountMetricExpressionParams, + BaseMetricExpressionParams, } from '../../../common/custom_threshold_rule/types'; import { ObservabilityPublicStart } from '../../plugin'; -import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; export interface AlertContextMeta { adHocDataViewList: DataView[]; - currentOptions?: Partial; - series?: MetricsExplorerSeries; } -export type MetricExpression = Omit< - MetricExpressionParams, - 'metric' | 'timeSize' | 'timeUnit' | 'metrics' | 'equation' -> & { - metric?: NonCountMetricExpressionParams['metric']; - metrics?: CustomMetricExpressionParams['metrics']; - label?: CustomMetricExpressionParams['label']; - equation?: CustomMetricExpressionParams['equation']; - timeSize?: MetricExpressionParams['timeSize']; - timeUnit?: MetricExpressionParams['timeUnit']; +export type MetricExpression = Omit & { + timeSize?: BaseMetricExpressionParams['timeSize']; + timeUnit?: BaseMetricExpressionParams['timeUnit']; }; export enum AGGREGATION_TYPES { @@ -122,45 +109,63 @@ export interface InfraClientStartDeps { export type RendererResult = React.ReactElement | null; export type RendererFunction = (args: RenderArgs) => Result; -export interface DerivedIndexPattern { - fields: MetricsSourceStatus['indexFields']; - title: string; -} - -export const SnapshotMetricTypeKeys = { - count: null, - cpu: null, - diskLatency: null, - load: null, - memory: null, - memoryTotal: null, - tx: null, - rx: null, - logRate: null, - diskIOReadBytes: null, - diskIOWriteBytes: null, - s3TotalRequests: null, - s3NumberOfObjects: null, - s3BucketSize: null, - s3DownloadBytes: null, - s3UploadBytes: null, - rdsConnections: null, - rdsQueriesExecuted: null, - rdsActiveTransactions: null, - rdsLatency: null, - sqsMessagesVisible: null, - sqsMessagesDelayed: null, - sqsMessagesSent: null, - sqsMessagesEmpty: null, - sqsOldestMessage: null, - custom: null, -}; -export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); - -export type SnapshotMetricType = rt.TypeOf; export interface CustomThresholdRuleTypeParams extends RuleTypeParams { - criteria: MetricExpressionParams[]; + criteria: CustomMetricExpressionParams[]; searchConfiguration: SerializedSearchSourceFields; groupBy?: string | string[]; } + +export const expressionTimestampsRT = rt.type({ + fromTimestamp: rt.number, + toTimestamp: rt.number, + interval: rt.string, + timeFieldName: rt.string, +}); +export type ExpressionTimestampsRT = rt.TypeOf; + +// Expression options +const aggType = rt.union([ + rt.literal('count'), + rt.literal('avg'), + rt.literal('sum'), + rt.literal('min'), + rt.literal('max'), + rt.literal('cardinality'), +]); +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: aggType, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); +const customThresholdExpressionMetricRT = rt.intersection([ + rt.type({ + aggregation: rt.string, + }), + rt.partial({ + field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, + }), +]); +export const expressionOptionsRT = rt.intersection([ + rt.type({ + aggregation: rt.string, + metrics: rt.array(customThresholdExpressionMetricRT), + }), + rt.partial({ + limit: rt.number, + groupBy: rt.union([rt.string, rt.array(rt.string)]), + filterQuery: rt.string, + source: rt.string, + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, + }), +]); + +export type ExpressionOptions = rt.TypeOf; diff --git a/x-pack/plugins/observability/public/utils/metrics_explorer.ts b/x-pack/plugins/observability/public/utils/metrics_explorer.ts index 136df77131811..525fdfc1a67e5 100644 --- a/x-pack/plugins/observability/public/utils/metrics_explorer.ts +++ b/x-pack/plugins/observability/public/utils/metrics_explorer.ts @@ -9,16 +9,9 @@ import { MetricsExplorerResponse, MetricsExplorerSeries, } from '../../common/custom_threshold_rule/metrics_explorer'; -import { - MetricsExplorerChartOptions, - MetricsExplorerChartType, - MetricsExplorerOptions, - MetricsExplorerTimeOptions, - MetricsExplorerTimestampsRT, - MetricsExplorerYAxisMode, -} from '../components/custom_threshold/hooks/use_metrics_explorer_options'; +import { ExpressionOptions, ExpressionTimestampsRT } from '../components/custom_threshold/types'; -export const options: MetricsExplorerOptions = { +export const options: ExpressionOptions = { limit: 3, groupBy: 'host.name', aggregation: 'avg', @@ -41,22 +34,15 @@ export const source = { }, anomalyThreshold: 20, }; - -export const chartOptions: MetricsExplorerChartOptions = { - type: MetricsExplorerChartType.line, - yAxisMode: MetricsExplorerYAxisMode.fromZero, - stack: false, -}; - export const derivedIndexPattern = { title: 'metricbeat-*', fields: [] }; -export const timeRange: MetricsExplorerTimeOptions = { - from: 'now-1h', - to: 'now', - interval: '>=10s', -}; +// export const timeRange: MetricsExplorerTimeOptions = { +// from: 'now-1h', +// to: 'now', +// interval: '>=10s', +// }; -export const mockedTimestamps: MetricsExplorerTimestampsRT = { +export const mockedTimestamps: ExpressionTimestampsRT = { fromTimestamp: 1678376367166, toTimestamp: 1678379973620, interval: '>=10s', diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index bbe534e2071e3..cd94678393c19 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -24,8 +24,8 @@ import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; import { Aggregators, Comparator, - CountMetricExpressionParams, - NonCountMetricExpressionParams, + CustomMetricExpressionParams, + CustomThresholdExpressionMetric, } from '../../../../common/custom_threshold_rule/types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); @@ -147,7 +147,7 @@ describe('The metric threshold alert type', () => { sourceId, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, }, @@ -163,10 +163,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire, @@ -246,7 +245,7 @@ describe('The metric threshold alert type', () => { comparator: Comparator, threshold: number[], groupBy: string[] = ['groupByField'], - metric?: string, + metrics?: CustomThresholdExpressionMetric[], state?: any ) => executor({ @@ -257,10 +256,10 @@ describe('The metric threshold alert type', () => { groupBy, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, - metric: metric ?? baseNonCountCriterion.metric, + metrics: metrics ?? customThresholdNonCountCriterion.metrics, }, ], }, @@ -272,10 +271,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -283,10 +281,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -303,10 +300,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1.5], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -314,10 +310,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1.5], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: false, @@ -334,10 +329,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [5], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: false, @@ -345,10 +339,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [5], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: false, @@ -365,10 +358,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -376,10 +368,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -400,10 +391,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -411,10 +408,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -422,10 +425,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'b' }, }, c: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -438,16 +447,21 @@ describe('The metric threshold alert type', () => { Comparator.GT, [0.75], ['groupByField'], - 'test.metric.2' + [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ] ); expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([])); setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -455,10 +469,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -466,10 +479,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'b' }, }, c: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -482,7 +494,13 @@ describe('The metric threshold alert type', () => { Comparator.GT, [0.75], ['groupByField'], - 'test.metric.1', + [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.1', + }, + ], stateResult1 ); expect(stateResult2.missingGroups).toEqual( @@ -491,10 +509,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -502,10 +519,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -518,7 +534,13 @@ describe('The metric threshold alert type', () => { Comparator.GT, [0.75], ['groupByField', 'groupByField-else'], - 'test.metric.1', + [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], stateResult2 ); expect(stateResult3.missingGroups).toEqual(expect.arrayContaining([])); @@ -528,7 +550,7 @@ describe('The metric threshold alert type', () => { comparator: Comparator, threshold: number[], filterQuery: string, - metric?: string, + metrics?: any, state?: any ) => executor({ @@ -539,10 +561,10 @@ describe('The metric threshold alert type', () => { groupBy: ['groupByField'], criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, - metric: metric ?? baseNonCountCriterion.metric, + metrics: metrics ?? customThresholdNonCountCriterion.metrics, }, ], searchConfiguration: { @@ -558,10 +580,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -569,10 +597,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -580,10 +614,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'b' }, }, c: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -602,10 +642,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -613,10 +652,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -624,10 +662,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'b' }, }, c: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -649,10 +686,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -660,10 +696,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -676,7 +711,13 @@ describe('The metric threshold alert type', () => { Comparator.GT, [0.75], JSON.stringify({ query: 'different' }), - 'test.metric.1', + [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.1', + }, + ], stateResult2 ); expect(stateResult3.groups).toEqual(expect.arrayContaining([])); @@ -689,7 +730,7 @@ describe('The metric threshold alert type', () => { comparator: Comparator, threshold: number[], groupBy: string[] = ['host.name'], - metric?: string, + metrics?: any, state?: any ) => executor({ @@ -700,10 +741,10 @@ describe('The metric threshold alert type', () => { groupBy, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, - metric: metric ?? baseNonCountCriterion.metric, + metrics: metrics ?? customThresholdNonCountCriterion.metrics, }, ], }, @@ -720,10 +761,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { 'host-01': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -734,10 +774,9 @@ describe('The metric threshold alert type', () => { }, }, 'host-02': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -771,7 +810,7 @@ describe('The metric threshold alert type', () => { comparator: Comparator, threshold: number[], groupBy: string = '', - metric?: string, + metrics?: any, state?: any ) => executor({ @@ -782,10 +821,10 @@ describe('The metric threshold alert type', () => { groupBy, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, - metric: metric ?? baseNonCountCriterion.metric, + metrics: metrics ?? customThresholdNonCountCriterion.metrics, }, ], }, @@ -800,10 +839,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.75], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -837,15 +875,21 @@ describe('The metric threshold alert type', () => { groupBy, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold: thresholdA, }, { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold: thresholdB, - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], }, ], }, @@ -854,10 +898,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [1.0], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -867,10 +910,9 @@ describe('The metric threshold alert type', () => { }, { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [3.0], - metric: 'test.metric.2', currentValue: 3.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -887,10 +929,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT_OR_EQ, threshold: [1.0], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -908,10 +949,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [1.0], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -919,10 +959,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [1.0], - metric: 'test.metric.1', currentValue: 3.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -932,10 +971,16 @@ describe('The metric threshold alert type', () => { }, { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [3.0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -943,10 +988,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [3.0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: false, @@ -965,10 +1016,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [1.0], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -978,10 +1028,16 @@ describe('The metric threshold alert type', () => { }, { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT_OR_EQ, threshold: [3.0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -1011,10 +1067,10 @@ describe('The metric threshold alert type', () => { sourceId, criteria: [ { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator, threshold, - } as CountMetricExpressionParams, + }, ], }, }); @@ -1022,10 +1078,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.GT, threshold: [0.9], - metric: 'count', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: true, @@ -1039,10 +1094,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.LT, threshold: [0.5], - metric: 'count', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: false, @@ -1070,7 +1124,7 @@ describe('The metric threshold alert type', () => { groupBy: 'groupByField', criteria: [ { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator, threshold, }, @@ -1085,10 +1139,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.LT_OR_EQ, threshold: [0], - metric: 'count', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: false, @@ -1096,10 +1149,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.LT_OR_EQ, threshold: [0], - metric: 'count', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: false, @@ -1114,10 +1166,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.LT_OR_EQ, threshold: [0], - metric: 'count', currentValue: 0, timestamp: new Date().toISOString(), shouldFire: true, @@ -1125,10 +1176,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.LT_OR_EQ, threshold: [0], - metric: 'count', currentValue: 0, timestamp: new Date().toISOString(), shouldFire: true, @@ -1154,11 +1204,17 @@ describe('The metric threshold alert type', () => { ...mockOptions.params, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, aggType: Aggregators.P99, - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], }, ], }, @@ -1167,10 +1223,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [1], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1184,10 +1246,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: false, @@ -1212,11 +1280,10 @@ describe('The metric threshold alert type', () => { sourceId, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator, threshold, aggType: Aggregators.P95, - metric: 'test.metric.1', }, ], }, @@ -1225,10 +1292,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0.25], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -1242,10 +1308,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [0.95], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: false, @@ -1270,10 +1335,16 @@ describe('The metric threshold alert type', () => { sourceId, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [1], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], }, ], alertOnNoData, @@ -1283,10 +1354,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1304,10 +1381,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1333,13 +1416,19 @@ describe('The metric threshold alert type', () => { sourceId, criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [1], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], }, { - ...baseCountCriterion, + ...customThresholdCountCriterion, comparator: Comparator.GT, threshold: [30], }, @@ -1351,10 +1440,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.LT, threshold: [1], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: STARTED_AT_MOCK_DATE.toISOString(), shouldFire: false, @@ -1370,7 +1465,7 @@ describe('The metric threshold alert type', () => { alertDetailsUrl: '', reason: 'test.metric.3 reported no data in the last 1m', timestamp: STARTED_AT_MOCK_DATE.toISOString(), - value: ['[NO DATA]', 0], + value: ['[NO DATA]', null], tags: [], }); expect(recentAction).toBeNoDataAction(); @@ -1383,7 +1478,7 @@ describe('The metric threshold alert type', () => { const instanceIdA = 'a'; const instanceIdB = 'b'; const instanceIdC = 'c'; - const execute = (metric: string, alertOnGroupDisappear: boolean = true, state?: any) => + const execute = (metrics: any, alertOnGroupDisappear: boolean = true, state?: any) => executor({ ...mockOptions, services, @@ -1393,10 +1488,10 @@ describe('The metric threshold alert type', () => { sourceId: 'default', criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric, + metrics, }, ], alertOnNoData: true, @@ -1418,10 +1513,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1435,10 +1536,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1452,10 +1559,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 1.0, timestamp: new Date().toISOString(), shouldFire: true, @@ -1463,10 +1569,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1488,10 +1593,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1499,10 +1610,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1520,10 +1637,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1531,10 +1654,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 1, timestamp: new Date().toISOString(), shouldFire: true, @@ -1542,10 +1671,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'b' }, }, c: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.2', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.2', + }, + ], currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1562,10 +1697,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: true, @@ -1573,10 +1707,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1593,7 +1726,7 @@ describe('The metric threshold alert type', () => { }); describe('if alertOnNoData is disabled but alertOnGroupDisappear is enabled', () => { - const executeWeirdNoDataConfig = (metric: string, state?: any) => + const executeWeirdNoDataConfig = (metrics: any, state?: any) => executor({ ...mockOptions, services, @@ -1603,10 +1736,10 @@ describe('The metric threshold alert type', () => { sourceId: 'default', criteria: [ { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric, + metrics, }, ], alertOnNoData: false, @@ -1624,10 +1757,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1641,10 +1780,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { '*': { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1658,10 +1803,9 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 1, timestamp: new Date().toISOString(), shouldFire: true, @@ -1669,10 +1813,9 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.1', currentValue: 3, timestamp: new Date().toISOString(), shouldFire: true, @@ -1692,10 +1835,16 @@ describe('The metric threshold alert type', () => { setEvaluationResults([ { a: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1703,10 +1852,16 @@ describe('The metric threshold alert type', () => { bucketKey: { groupBy0: 'a' }, }, b: { - ...baseNonCountCriterion, + ...customThresholdNonCountCriterion, comparator: Comparator.GT, threshold: [0], - metric: 'test.metric.3', + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.3', + }, + ], currentValue: null, timestamp: new Date().toISOString(), shouldFire: false, @@ -1853,19 +2008,31 @@ declare global { } } -const baseNonCountCriterion = { - aggType: Aggregators.AVERAGE, - metric: 'test.metric.1', +const customThresholdNonCountCriterion: CustomMetricExpressionParams = { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + metrics: [ + { + aggType: Aggregators.AVERAGE, + name: 'A', + field: 'test.metric.1', + }, + ], timeSize: 1, timeUnit: 'm', threshold: [0], - comparator: Comparator.GT, -} as NonCountMetricExpressionParams; +}; -const baseCountCriterion = { - aggType: Aggregators.COUNT, +const customThresholdCountCriterion: CustomMetricExpressionParams = { + aggType: Aggregators.CUSTOM, + comparator: Comparator.GT, + metrics: [ + { + aggType: Aggregators.COUNT, + name: 'A', + }, + ], timeSize: 1, timeUnit: 'm', threshold: [0], - comparator: Comparator.GT, -} as CountMetricExpressionParams; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 0e0a4a9a3e7eb..72040f767aca5 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -270,9 +270,7 @@ export const createCustomThresholdExecutor = ({ timestamp, value: alertResults.map((result, index) => { const evaluation = result[group]; - if (!evaluation && criteria[index].aggType === 'count') { - return 0; - } else if (!evaluation) { + if (!evaluation) { return null; } return formatAlertResult(evaluation).currentValue; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts index 3066276316921..9a1dde442983c 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts @@ -5,33 +5,19 @@ * 2.0. */ -import { - Aggregators, - MetricExpressionParams, -} from '../../../../../common/custom_threshold_rule/types'; +import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import { createConditionScript } from './create_condition_script'; import { createLastPeriod } from './wrap_in_period'; export const createBucketSelector = ( - condition: MetricExpressionParams, + condition: CustomMetricExpressionParams, alertOnGroupDisappear: boolean = false, timeFieldName: string, groupBy?: string | string[], lastPeriodEnd?: number ) => { const hasGroupBy = !!groupBy; - const isPercentile = [Aggregators.P95, Aggregators.P99].includes(condition.aggType); - const isCount = condition.aggType === Aggregators.COUNT; - const isRate = condition.aggType === Aggregators.RATE; - const bucketPath = isCount - ? "currentPeriod['all']>_count" - : isRate - ? `aggregatedValue` - : isPercentile - ? `currentPeriod[\'all\']>aggregatedValue[${ - condition.aggType === Aggregators.P95 ? '95' : '99' - }]` - : "currentPeriod['all']>aggregatedValue"; + const bucketPath = "currentPeriod['all']>aggregatedValue"; const shouldTrigger = { bucket_script: { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 513ce51f0fd4c..d90c6ba0caef3 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -8,21 +8,8 @@ import moment from 'moment'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { - Aggregators, - CustomMetricExpressionParams, - MetricExpressionParams, -} from '../../../../../common/custom_threshold_rule/types'; +import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import { AdditionalContext, getIntervalInSeconds } from '../utils'; -import { - AVERAGE_I18N, - CARDINALITY_I18N, - CUSTOM_EQUATION_I18N, - DOCUMENT_COUNT_I18N, - MAX_I18N, - MIN_I18N, - SUM_I18N, -} from '../translations'; import { SearchConfigurationType } from '../types'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; @@ -34,8 +21,7 @@ export interface EvaluatedRuleParams { searchConfiguration: SearchConfigurationType; } -export type Evaluation = Omit & { - metric: string; +export type Evaluation = CustomMetricExpressionParams & { currentValue: number | null; timestamp: string; shouldFire: boolean; @@ -44,26 +30,6 @@ export type Evaluation = Omit & { context?: AdditionalContext; }; -const getMetric = (criterion: CustomMetricExpressionParams) => { - if (!criterion.label && criterion.metrics.length === 1) { - switch (criterion.metrics[0].aggType) { - case Aggregators.COUNT: - return DOCUMENT_COUNT_I18N; - case Aggregators.AVERAGE: - return AVERAGE_I18N(criterion.metrics[0].field!); - case Aggregators.MAX: - return MAX_I18N(criterion.metrics[0].field!); - case Aggregators.MIN: - return MIN_I18N(criterion.metrics[0].field!); - case Aggregators.CARDINALITY: - return CARDINALITY_I18N(criterion.metrics[0].field!); - case Aggregators.SUM: - return SUM_I18N(criterion.metrics[0].field!); - } - } - return criterion.label || CUSTOM_EQUATION_I18N; -}; - export const evaluateRule = async ( esClient: ElasticsearchClient, params: Params, @@ -132,7 +98,6 @@ export const evaluateRule = async & { @@ -15,30 +16,27 @@ export type FormattedEvaluation = Omit }; export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvaluation => { - const { metric, currentValue, threshold, comparator } = evaluationResult; + const { metrics, currentValue, threshold, comparator } = evaluationResult; const noDataValue = i18n.translate( 'xpack.observability.customThreshold.rule.alerting.threshold.noDataFormattedValue', { defaultMessage: '[NO DATA]' } ); - if (metric.endsWith('.pct')) { - const formatter = createFormatter('percent'); - return { - ...evaluationResult, - currentValue: - currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, - threshold: Array.isArray(threshold) - ? threshold.map((v: number) => formatter(v)) - : [formatter(threshold)], - comparator, - }; + let formatter = createFormatter('highPrecision'); + let label = evaluationResult.label || CUSTOM_EQUATION_I18N; + + if (metrics.length === 1 && metrics[0].field) { + if (metrics[0].field.endsWith('.pct')) { + formatter = createFormatter('percent'); + } + label = evaluationResult.label || metrics[0].field; } - const formatter = createFormatter('highPrecision'); return { ...evaluationResult, currentValue: currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + label: label || CUSTOM_EQUATION_I18N, threshold: Array.isArray(threshold) ? threshold.map((v: number) => formatter(v)) : [formatter(threshold)], diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts index a76c45e4e4583..2e23ba5ae45dd 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts @@ -6,11 +6,11 @@ */ import moment from 'moment'; -import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; +import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; export const createLastPeriod = ( lastPeriodEnd: number, - { timeUnit, timeSize }: MetricExpressionParams, + { timeUnit, timeSize }: CustomMetricExpressionParams, timeFieldName: string ) => { const start = moment(lastPeriodEnd).subtract(timeSize, timeUnit).toISOString(); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts index 4a8d7a2c2dc8a..b35fc2ca58234 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts @@ -10,7 +10,13 @@ import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { formatDurationFromTimeUnitChar } from '../../../../common'; import { Evaluation } from './lib/evaluate_rule'; import { formatAlertResult, FormattedEvaluation } from './lib/format_alert_result'; -import { BELOW_TEXT, ABOVE_TEXT, BETWEEN_TEXT, NOT_BETWEEN_TEXT } from './translations'; +import { + BELOW_TEXT, + ABOVE_TEXT, + BETWEEN_TEXT, + NOT_BETWEEN_TEXT, + CUSTOM_EQUATION_I18N, +} from './translations'; import { UNGROUPED_FACTORY_KEY } from './constants'; const toNumber = (value: number | string) => @@ -103,17 +109,15 @@ export const buildFiredAlertReason: ( }; const buildAggregationReason: (evaluation: FormattedEvaluation) => string = ({ - metric, + label, comparator, threshold, currentValue, - timeSize, - timeUnit, }) => i18n.translate('xpack.observability.customThreshold.rule.threshold.firedAlertReason', { - defaultMessage: '{metric} is {currentValue}, {comparator} the threshold of {threshold}', + defaultMessage: '{label} is {currentValue}, {comparator} the threshold of {threshold}', values: { - metric, + label, comparator: alertComparatorToI18n(comparator), threshold: thresholdToI18n(threshold), currentValue, @@ -123,16 +127,16 @@ const buildAggregationReason: (evaluation: FormattedEvaluation) => string = ({ // Once recovered reason messages are re-enabled, checkout this issue https://github.com/elastic/kibana/issues/121272 regarding latest reason format export const buildRecoveredAlertReason: (alertResult: { group: string; - metric: string; + label?: string; comparator: Comparator; threshold: Array; currentValue: number | string; -}) => string = ({ group, metric, comparator, threshold, currentValue }) => +}) => string = ({ group, label = CUSTOM_EQUATION_I18N, comparator, threshold, currentValue }) => i18n.translate('xpack.observability.customThreshold.rule.threshold.recoveredAlertReason', { defaultMessage: - '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', + '{label} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', values: { - metric, + label, comparator: recoveredComparatorToI18n( comparator, threshold.map(toNumber), @@ -144,16 +148,17 @@ export const buildRecoveredAlertReason: (alertResult: { }, }); -export const buildNoDataAlertReason: (alertResult: { - group: string; - metric: string; - timeSize: number; - timeUnit: string; -}) => string = ({ group, metric, timeSize, timeUnit }) => +export const buildNoDataAlertReason: (alertResult: Evaluation & { group: string }) => string = ({ + group, + label = CUSTOM_EQUATION_I18N, + metrics, + timeSize, + timeUnit, +}) => i18n.translate('xpack.observability.customThreshold.rule.threshold.noDataAlertReason', { - defaultMessage: '{metric} reported no data in the last {interval}{group}', + defaultMessage: '{label} reported no data in the last {interval}{group}', values: { - metric, + label: metrics.length === 1 && metrics[0].field ? metrics[0].field : label, interval: `${timeSize}${timeUnit}`, group: formatGroup(group), }, From 881a6c1e2554af3d7de08ef8df1bb0ed020db768 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:26:50 +0000 Subject: [PATCH 08/17] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 4c85d57f77926..ac2fb5c215ded 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -70,7 +70,6 @@ "@kbn/exploratory-view-plugin", "@kbn/rison", "@kbn/io-ts-utils", - "@kbn/ml-anomaly-utils", "@kbn/observability-alert-details", "@kbn/ui-actions-plugin", "@kbn/field-types", From 886a4ed4b5f8c5a35e7363436a98002220eb411a Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 1 Nov 2023 17:51:27 +0100 Subject: [PATCH 09/17] Fix pct field format and rearrange types --- .../custom_threshold_rule/metrics_explorer.ts | 146 ------------------ .../common/custom_threshold_rule/types.ts | 14 -- .../custom_equation_editor.tsx | 2 +- .../components/expression_chart.tsx | 2 +- .../components/expression_row.test.tsx | 2 +- .../components/expression_row.tsx | 12 +- .../components/series_chart.tsx | 2 +- .../helpers/calculate_domain.ts | 2 +- .../hooks/mocks}/metrics_explorer.ts | 11 +- .../hooks/use_expression_chart_data.ts | 15 +- .../hooks/use_expression_data.ts | 9 +- .../hooks/use_metrics_explorer_data.test.tsx | 5 +- .../components/custom_threshold/types.ts | 77 ++++++++- .../lib/format_alert_result.ts | 41 ++++- .../common/expression_items/threshold.tsx | 7 +- 15 files changed, 140 insertions(+), 207 deletions(-) delete mode 100644 x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts rename x-pack/plugins/observability/public/{utils => components/custom_threshold/hooks/mocks}/metrics_explorer.ts (85%) diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts deleted file mode 100644 index 89756f6bad264..0000000000000 --- a/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts +++ /dev/null @@ -1,146 +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 * as rt from 'io-ts'; -import { xor } from 'lodash'; -import { METRIC_EXPLORER_AGGREGATIONS } from './constants'; - -export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; - -type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; - -const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< - Record ->((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); - -export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); - -export type MetricExplorerCustomMetricAggregations = Exclude< - MetricsExplorerAggregation, - 'custom' | 'rate' | 'p95' | 'p99' ->; -const metricsExplorerCustomMetricAggregationKeys = xor( - METRIC_EXPLORER_AGGREGATIONS, - OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS -).reduce>( - (acc, agg) => ({ ...acc, [agg]: null }), - {} as Record -); -export const metricsExplorerCustomMetricAggregationRT = rt.keyof( - metricsExplorerCustomMetricAggregationKeys -); - -export const metricsExplorerMetricRequiredFieldsRT = rt.type({ - aggregation: metricsExplorerAggregationRT, -}); - -export const metricsExplorerCustomMetricRT = rt.intersection([ - rt.type({ - name: rt.string, - aggregation: metricsExplorerCustomMetricAggregationRT, - }), - rt.partial({ - field: rt.string, - filter: rt.string, - }), -]); - -export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ - field: rt.union([rt.string, rt.undefined]), - metrics: rt.array(metricsExplorerCustomMetricRT), - equation: rt.string, -}); - -export const metricsExplorerMetricRT = rt.intersection([ - metricsExplorerMetricRequiredFieldsRT, - metricsExplorerMetricOptionalFieldsRT, -]); - -export const timeRangeRT = rt.type({ - from: rt.number, - to: rt.number, - interval: rt.string, -}); - -export const metricsExplorerRequestBodyRequiredFieldsRT = rt.type({ - timerange: timeRangeRT, - indexPattern: rt.string, - metrics: rt.array(metricsExplorerMetricRT), -}); - -const groupByRT = rt.union([rt.string, rt.null, rt.undefined]); -export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); - -export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ - groupBy: rt.union([groupByRT, rt.array(groupByRT)]), - afterKey: rt.union([rt.string, rt.null, rt.undefined, afterKeyObjectRT]), - limit: rt.union([rt.number, rt.null, rt.undefined]), - filterQuery: rt.union([rt.string, rt.null, rt.undefined]), - forceInterval: rt.boolean, - dropLastBucket: rt.boolean, -}); - -export const metricsExplorerRequestBodyRT = rt.intersection([ - metricsExplorerRequestBodyRequiredFieldsRT, - metricsExplorerRequestBodyOptionalFieldsRT, -]); - -export const metricsExplorerPageInfoRT = rt.type({ - total: rt.number, - afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), -}); - -export const metricsExplorerColumnTypeRT = rt.keyof({ - date: null, - number: null, - string: null, -}); - -export const metricsExplorerColumnRT = rt.type({ - name: rt.string, - type: metricsExplorerColumnTypeRT, -}); - -export const metricsExplorerRowRT = rt.intersection([ - rt.type({ - timestamp: rt.number, - }), - rt.record( - rt.string, - rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) - ), -]); - -export const metricsExplorerSeriesRT = rt.intersection([ - rt.type({ - id: rt.string, - columns: rt.array(metricsExplorerColumnRT), - rows: rt.array(metricsExplorerRowRT), - }), - rt.partial({ - keys: rt.array(rt.string), - }), -]); - -export const metricsExplorerResponseRT = rt.type({ - series: rt.array(metricsExplorerSeriesRT), - pageInfo: metricsExplorerPageInfoRT, -}); - -export type AfterKey = rt.TypeOf; - -export type MetricsExplorerAggregation = rt.TypeOf; - -export type MetricsExplorerMetric = rt.TypeOf; - -export type MetricsExplorerRow = rt.TypeOf; - -export type MetricsExplorerSeries = rt.TypeOf; - -export type MetricsExplorerRequestBody = rt.TypeOf; - -export type MetricsExplorerResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index 8dc5c7e644a6f..bfbc685fa59be 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -6,10 +6,7 @@ */ import * as rt from 'io-ts'; -import { values } from 'lodash'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; -import { Color } from './color_palette'; -import { metricsExplorerMetricRT } from './metrics_explorer'; import { TimeUnitChar } from '../utils/formatters/duration'; type DeepPartialArray = Array>; @@ -94,17 +91,6 @@ export enum Aggregators { CUSTOM = 'custom', } -const metricsExplorerOptionsMetricRT = rt.intersection([ - metricsExplorerMetricRT, - rt.partial({ - rate: rt.boolean, - color: rt.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), - label: rt.string, - }), -]); - -export type MetricsExplorerOptionsMetric = rt.TypeOf; - export enum MetricsExplorerChartType { line = 'line', area = 'area', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index f3635f39cf3bc..835a5669fbb83 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -22,7 +22,7 @@ import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/custom_threshold_rule/metrics_explorer'; +import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../types'; import { Aggregators, CustomMetricAggTypes, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx index f236700f62b26..c18b2860df95d 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx @@ -25,7 +25,6 @@ import { first, last } from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../utils/kibana_react'; -import { MetricsExplorerRow } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { Color } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; @@ -42,6 +41,7 @@ import { ThresholdAnnotations } from './criterion_preview_chart/threshold_annota import { CUSTOM_EQUATION } from '../i18n_strings'; import { calculateDomain } from '../helpers/calculate_domain'; import { MetricExplorerSeriesChart } from './series_chart'; +import { MetricsExplorerRow } from '../types'; interface Props { expression: MetricExpression; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx index fc80e201afa38..f0c86bc1430c1 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx @@ -75,7 +75,7 @@ describe('ExpressionRow', () => { wrapper .html() .match( - '50' + '50%' ) ?? []; expect(valueMatch).toBeTruthy(); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index 5521874bbb497..1bca761c1126a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -12,7 +12,6 @@ import { EuiFlexItem, EuiFormRow, EuiSpacer, - EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo, useState } from 'react'; @@ -221,17 +220,8 @@ const ThresholdElement: React.FC<{ onChangeSelectedThreshold={updateThreshold} errors={errors} display="fullWidth" + isMetricPct={isMetricPct} /> - - {isMetricPct && ( -
- % -
- )} ); }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx index 6ed310823863c..b5049788dd2a2 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx @@ -14,7 +14,7 @@ import { AreaSeriesStyle, BarSeriesStyle, } from '@elastic/charts'; -import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { MetricsExplorerSeries } from '../types'; import { Color, colorTransformer } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts index 1794caa599005..8de019598bc6e 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts @@ -6,7 +6,7 @@ */ import { min, max, isNumber } from 'lodash'; -import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { MetricsExplorerSeries } from '../types'; const getMin = (values: Array) => { const minValue = min(values); diff --git a/x-pack/plugins/observability/public/utils/metrics_explorer.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/mocks/metrics_explorer.ts similarity index 85% rename from x-pack/plugins/observability/public/utils/metrics_explorer.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/mocks/metrics_explorer.ts index 525fdfc1a67e5..79e281717ab84 100644 --- a/x-pack/plugins/observability/public/utils/metrics_explorer.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/mocks/metrics_explorer.ts @@ -6,10 +6,11 @@ */ import { + ExpressionOptions, + ExpressionTimestampsRT, MetricsExplorerResponse, MetricsExplorerSeries, -} from '../../common/custom_threshold_rule/metrics_explorer'; -import { ExpressionOptions, ExpressionTimestampsRT } from '../components/custom_threshold/types'; +} from '../../types'; export const options: ExpressionOptions = { limit: 3, @@ -36,12 +37,6 @@ export const source = { }; export const derivedIndexPattern = { title: 'metricbeat-*', fields: [] }; -// export const timeRange: MetricsExplorerTimeOptions = { -// from: 'now-1h', -// to: 'now', -// interval: '>=10s', -// }; - export const mockedTimestamps: ExpressionTimestampsRT = { fromTimestamp: 1678376367166, toTimestamp: 1678379973620, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts index 589ccbf5b798c..8d8832c4c251a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts @@ -8,9 +8,14 @@ import DateMath from '@kbn/datemath'; import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; -import { MetricExplorerCustomMetricAggregations } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; -import { ExpressionOptions, ExpressionTimestampsRT, MetricExpression, TimeRange } from '../types'; +import { + ExpressionOptions, + ExpressionTimestampsRT, + MetricExpression, + MetricsExplorerMetricRT, + TimeRange, +} from '../types'; import { useExpressionData } from './use_expression_data'; const DEFAULT_TIME_RANGE = {}; @@ -73,18 +78,18 @@ export const useExpressionChartData = ( const mapCustomThresholdMetricToMetricsExplorerMetric = ( metric: CustomThresholdExpressionMetric -) => { +): MetricsExplorerMetricRT => { if (metric.aggType === 'count') { return { name: metric.name, - aggregation: 'count' as MetricExplorerCustomMetricAggregations, + aggregation: 'count', filter: metric.filter, }; } return { name: metric.name, - aggregation: metric.aggType as MetricExplorerCustomMetricAggregations, + aggregation: metric.aggType, field: metric.field, }; }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts index ae077e90d7dd0..e6058a124e086 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_data.ts @@ -9,13 +9,14 @@ import { DataViewBase } from '@kbn/es-query'; import { useInfiniteQuery } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { convertKueryToElasticSearchQuery } from '../helpers/kuery'; +import { decodeOrThrow } from '../helpers/runtime_types'; import { + ExpressionOptions, + ExpressionTimestampsRT, MetricsExplorerResponse, metricsExplorerResponseRT, -} from '../../../../common/custom_threshold_rule/metrics_explorer'; -import { convertKueryToElasticSearchQuery } from '../helpers/kuery'; -import { decodeOrThrow } from '../helpers/runtime_types'; -import { ExpressionOptions, ExpressionTimestampsRT } from '../types'; +} from '../types'; export function useExpressionData( options: ExpressionOptions, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx index cf08b1ac27743..b7e750875a5ff 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx @@ -6,20 +6,19 @@ */ import React from 'react'; +import { DataViewBase } from '@kbn/es-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; - import { ExpressionOptions, ExpressionTimestampsRT } from '../types'; import { useExpressionData } from './use_expression_data'; -import { DataViewBase } from '@kbn/es-query'; import { createSeries, derivedIndexPattern, mockedTimestamps, options, resp, -} from '../../../utils/metrics_explorer'; +} from './mocks/metrics_explorer'; const mockedFetch = jest.fn(); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts index 08f133d07734d..3fd027d97065e 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -124,7 +124,9 @@ export const expressionTimestampsRT = rt.type({ }); export type ExpressionTimestampsRT = rt.TypeOf; -// Expression options +/* + * Expression options + */ const aggType = rt.union([ rt.literal('count'), rt.literal('avg'), @@ -133,7 +135,7 @@ const aggType = rt.union([ rt.literal('max'), rt.literal('cardinality'), ]); -export const metricsExplorerCustomMetricRT = rt.intersection([ +export const metricsExplorerMetricRT = rt.intersection([ rt.type({ name: rt.string, aggregation: aggType, @@ -149,7 +151,7 @@ const customThresholdExpressionMetricRT = rt.intersection([ }), rt.partial({ field: rt.union([rt.string, rt.undefined]), - custom_metrics: rt.array(metricsExplorerCustomMetricRT), + custom_metrics: rt.array(metricsExplorerMetricRT), equation: rt.string, }), ]); @@ -168,4 +170,73 @@ export const expressionOptionsRT = rt.intersection([ }), ]); +export type AggType = rt.TypeOf; +export type MetricsExplorerMetricRT = rt.TypeOf; export type ExpressionOptions = rt.TypeOf; +/* + * End of expression options + */ + +/* + * Metrics explorer types + */ +export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; + +export const timeRangeRT = rt.type({ + from: rt.number, + to: rt.number, + interval: rt.string, +}); + +export const afterKeyObjectRT = rt.record(rt.string, rt.union([rt.string, rt.null])); + +export const metricsExplorerPageInfoRT = rt.type({ + total: rt.number, + afterKey: rt.union([rt.string, rt.null, afterKeyObjectRT]), +}); + +export const metricsExplorerColumnTypeRT = rt.keyof({ + date: null, + number: null, + string: null, +}); + +export const metricsExplorerColumnRT = rt.type({ + name: rt.string, + type: metricsExplorerColumnTypeRT, +}); + +export const metricsExplorerRowRT = rt.intersection([ + rt.type({ + timestamp: rt.number, + }), + rt.record( + rt.string, + rt.union([rt.string, rt.number, rt.null, rt.undefined, rt.array(rt.object)]) + ), +]); + +export const metricsExplorerSeriesRT = rt.intersection([ + rt.type({ + id: rt.string, + columns: rt.array(metricsExplorerColumnRT), + rows: rt.array(metricsExplorerRowRT), + }), + rt.partial({ + keys: rt.array(rt.string), + }), +]); + +export const metricsExplorerResponseRT = rt.type({ + series: rt.array(metricsExplorerSeriesRT), + pageInfo: metricsExplorerPageInfoRT, +}); + +export type MetricsExplorerRow = rt.TypeOf; + +export type MetricsExplorerSeries = rt.TypeOf; + +export type MetricsExplorerResponse = rt.TypeOf; +/* + * End of metrics explorer types + */ diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts index c55d144431ceb..3b35f2ba3f647 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts @@ -6,8 +6,17 @@ */ import { i18n } from '@kbn/i18n'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; import { createFormatter } from '../../../../../common/custom_threshold_rule/formatters'; -import { CUSTOM_EQUATION_I18N } from '../translations'; +import { + AVERAGE_I18N, + CARDINALITY_I18N, + CUSTOM_EQUATION_I18N, + DOCUMENT_COUNT_I18N, + MAX_I18N, + MIN_I18N, + SUM_I18N, +} from '../translations'; import { Evaluation } from './evaluate_rule'; export type FormattedEvaluation = Omit & { @@ -15,6 +24,26 @@ export type FormattedEvaluation = Omit threshold: string[]; }; +const getLabel = (criterion: Evaluation) => { + if (!criterion.label && criterion.metrics.length === 1) { + switch (criterion.metrics[0].aggType) { + case Aggregators.COUNT: + return DOCUMENT_COUNT_I18N; + case Aggregators.AVERAGE: + return AVERAGE_I18N(criterion.metrics[0].field!); + case Aggregators.MAX: + return MAX_I18N(criterion.metrics[0].field!); + case Aggregators.MIN: + return MIN_I18N(criterion.metrics[0].field!); + case Aggregators.CARDINALITY: + return CARDINALITY_I18N(criterion.metrics[0].field!); + case Aggregators.SUM: + return SUM_I18N(criterion.metrics[0].field!); + } + } + return criterion.label || CUSTOM_EQUATION_I18N; +}; + export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvaluation => { const { metrics, currentValue, threshold, comparator } = evaluationResult; const noDataValue = i18n.translate( @@ -23,13 +52,11 @@ export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvalua ); let formatter = createFormatter('highPrecision'); - let label = evaluationResult.label || CUSTOM_EQUATION_I18N; + const label = getLabel(evaluationResult); + console.log('label', label); - if (metrics.length === 1 && metrics[0].field) { - if (metrics[0].field.endsWith('.pct')) { - formatter = createFormatter('percent'); - } - label = evaluationResult.label || metrics[0].field; + if (metrics.length === 1 && metrics[0].field && metrics[0].field.endsWith('.pct')) { + formatter = createFormatter('percent'); } return { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index 9f43a652cb2d4..01bc4b6273561 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -46,6 +46,7 @@ export interface ThresholdExpressionProps { | 'rightUp' | 'rightDown'; display?: 'fullWidth' | 'inline'; + isMetricPct?: boolean; } export const ThresholdExpression = ({ @@ -57,6 +58,7 @@ export const ThresholdExpression = ({ display = 'inline', threshold = [], popupPosition, + isMetricPct = false, }: ThresholdExpressionProps) => { const comparators = customComparators ?? builtInComparators; const [alertThresholdPopoverOpen, setAlertThresholdPopoverOpen] = useState(false); @@ -88,7 +90,10 @@ export const ThresholdExpression = ({ Date: Wed, 1 Nov 2023 20:23:38 +0100 Subject: [PATCH 10/17] Fix no data reason and refactor threshold component --- .../custom_threshold_executor.test.ts | 10 ++++++---- .../custom_threshold_executor.ts | 6 ++++-- .../custom_threshold/lib/format_alert_result.ts | 3 +-- .../lib/rules/custom_threshold/messages.ts | 3 +-- .../common/expression_items/threshold.test.tsx | 17 +++++++++++++++++ .../common/expression_items/threshold.tsx | 7 +++---- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index cd94678393c19..e78d5e121558b 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -234,7 +234,7 @@ describe('The metric threshold alert type', () => { const { action } = mostRecentAction(instanceID); expect(action.group).toBeUndefined(); expect(action.reason).toBe( - 'test.metric.1 is 1, above the threshold of 0.75. (duration: 1 min, data view: mockedIndexPattern)' + 'Average test.metric.1 is 1, above the threshold of 0.75. (duration: 1 min, data view: mockedIndexPattern)' ); }); }); @@ -1051,7 +1051,7 @@ describe('The metric threshold alert type', () => { const { action } = mostRecentAction(instanceID); const reasons = action.reason; expect(reasons).toBe( - 'test.metric.1 is 1, above the threshold of 1; test.metric.2 is 3, above the threshold of 3. (duration: 1 min, data view: mockedIndexPattern)' + 'Average test.metric.1 is 1, above the threshold of 1; Average test.metric.2 is 3, above the threshold of 3. (duration: 1 min, data view: mockedIndexPattern)' ); }); }); @@ -1374,7 +1374,9 @@ describe('The metric threshold alert type', () => { ]); await execute(true); const recentAction = mostRecentAction(instanceID); - expect(recentAction.action.reason).toEqual('test.metric.3 reported no data in the last 1m'); + expect(recentAction.action.reason).toEqual( + 'Average test.metric.3 reported no data in the last 1m' + ); expect(recentAction).toBeNoDataAction(); }); test('does not send a No Data alert when not configured to do so', async () => { @@ -1463,7 +1465,7 @@ describe('The metric threshold alert type', () => { const recentAction = mostRecentAction(instanceID); expect(recentAction.action).toEqual({ alertDetailsUrl: '', - reason: 'test.metric.3 reported no data in the last 1m', + reason: 'Average test.metric.3 reported no data in the last 1m', timestamp: STARTED_AT_MOCK_DATE.toISOString(), value: ['[NO DATA]', null], tags: [], diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 72040f767aca5..fffe6115ab303 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -43,7 +43,7 @@ import { getFormattedGroupBy, } from './utils'; -import { formatAlertResult } from './lib/format_alert_result'; +import { formatAlertResult, getLabel } from './lib/format_alert_result'; import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; import { MissingGroupsRecord } from './lib/check_missing_group'; import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; @@ -214,7 +214,9 @@ export const createCustomThresholdExecutor = ({ if (nextState === AlertStates.NO_DATA) { reason = alertResults .filter((result) => result[group]?.isNoData) - .map((result) => buildNoDataAlertReason({ ...result[group], group })) + .map((result) => + buildNoDataAlertReason({ ...result[group], label: getLabel(result[group]), group }) + ) .join('\n'); } } diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts index 3b35f2ba3f647..c0220d89c9d98 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts @@ -24,7 +24,7 @@ export type FormattedEvaluation = Omit threshold: string[]; }; -const getLabel = (criterion: Evaluation) => { +export const getLabel = (criterion: Evaluation) => { if (!criterion.label && criterion.metrics.length === 1) { switch (criterion.metrics[0].aggType) { case Aggregators.COUNT: @@ -53,7 +53,6 @@ export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvalua let formatter = createFormatter('highPrecision'); const label = getLabel(evaluationResult); - console.log('label', label); if (metrics.length === 1 && metrics[0].field && metrics[0].field.endsWith('.pct')) { formatter = createFormatter('percent'); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts index b35fc2ca58234..973ce8f57093a 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts @@ -151,14 +151,13 @@ export const buildRecoveredAlertReason: (alertResult: { export const buildNoDataAlertReason: (alertResult: Evaluation & { group: string }) => string = ({ group, label = CUSTOM_EQUATION_I18N, - metrics, timeSize, timeUnit, }) => i18n.translate('xpack.observability.customThreshold.rule.threshold.noDataAlertReason', { defaultMessage: '{label} reported no data in the last {interval}{group}', values: { - label: metrics.length === 1 && metrics[0].field ? metrics[0].field : label, + label, interval: `${timeSize}${timeUnit}`, group: formatGroup(group), }, diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index 9988fd94eee59..7e8eb5d6feee0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -115,6 +115,23 @@ describe('threshold expression', () => { expect(onChangeSelectedThresholdComparator).toHaveBeenCalled(); }); + it('renders threshold unit correctly', async () => { + const wrapper = mountWithIntl( + '} + threshold={[10]} + errors={{ threshold0: [], threshold1: [] }} + onChangeSelectedThreshold={jest.fn()} + onChangeSelectedThresholdComparator={jest.fn()} + unit="%" + /> + ); + + expect(wrapper.find('[data-test-subj="thresholdPopover"]').last().text()).toMatchInlineSnapshot( + `"Is above 10%"` + ); + }); + it('renders the correct number of threshold inputs', async () => { const wrapper = mountWithIntl( { const comparators = customComparators ?? builtInComparators; const [alertThresholdPopoverOpen, setAlertThresholdPopoverOpen] = useState(false); @@ -91,8 +91,7 @@ export const ThresholdExpression = ({ data-test-subj="thresholdPopover" description={comparators[comparator].text} value={ - (threshold || []).slice(0, numRequiredThresholds).join(` ${andThresholdText} `) + - (isMetricPct ? '%' : '') + (threshold || []).slice(0, numRequiredThresholds).join(` ${andThresholdText} `) + unit } isActive={Boolean( alertThresholdPopoverOpen || From c4e25e85ea1b19ae3c897a2d51bb30acf792db25 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 1 Nov 2023 21:20:04 +0100 Subject: [PATCH 11/17] Fix unit props --- .../components/custom_threshold/components/expression_row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index 1bca761c1126a..ee6dcbf44d4c0 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -220,7 +220,7 @@ const ThresholdElement: React.FC<{ onChangeSelectedThreshold={updateThreshold} errors={errors} display="fullWidth" - isMetricPct={isMetricPct} + unit={isMetricPct ? '%' : ''} /> ); From d2417fcdf881b6a641670b97b73fa63bae07e420 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Thu, 2 Nov 2023 15:36:53 +0100 Subject: [PATCH 12/17] Remove aggregators that are not supported in the custom threshold --- .../common/custom_threshold_rule/constants.ts | 2 + .../common/custom_threshold_rule/types.ts | 55 ++++--- .../custom_equation_editor.stories.tsx | 5 +- .../custom_equation_editor.tsx | 10 +- .../custom_equation/metric_row_with_agg.tsx | 9 +- .../components/expression_chart.test.tsx | 3 +- .../components/expression_row.test.tsx | 5 +- .../components/expression_row.tsx | 22 ++- .../custom_threshold/components/group_by.tsx | 1 - .../custom_threshold_rule_expression.tsx | 7 +- .../hooks/use_expression_chart_data.ts | 12 +- .../mocks/custom_threshold_rule.ts | 11 +- .../components/custom_threshold/types.ts | 43 +----- .../custom_threshold_executor.test.ts | 135 +----------------- .../lib/create_percentile_aggregation.ts | 24 ---- .../lib/create_timerange.test.ts | 41 +----- .../custom_threshold/lib/create_timerange.ts | 6 +- .../custom_threshold/lib/evaluate_rule.ts | 7 +- .../custom_threshold/lib/metric_query.test.ts | 5 +- .../register_custom_threshold_rule_type.ts | 3 +- .../lib/rules/custom_threshold/types.ts | 23 --- .../custom_threshold_rule/avg_pct_fired.ts | 3 +- .../custom_threshold_rule/avg_pct_no_data.ts | 3 +- .../custom_threshold_rule/avg_us_fired.ts | 3 +- .../custom_eq_avg_bytes_fired.ts | 5 +- .../documents_count_fired.ts | 3 +- .../custom_threshold_rule/group_by_fired.ts | 3 +- .../custom_threshold_rule_data_view.ts | 3 +- .../helpers/alerting_api_helper.ts | 5 +- .../observability/metric_threshold_rule.ts | 9 +- .../custom_threshold_rule/avg_pct_fired.ts | 3 +- .../custom_threshold_rule/avg_pct_no_data.ts | 3 +- .../custom_eq_avg_bytes_fired.ts | 3 +- .../documents_count_fired.ts | 3 +- .../custom_threshold_rule/group_by_fired.ts | 3 +- 35 files changed, 119 insertions(+), 362 deletions(-) delete mode 100644 x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts index fa31ea50b69dd..7f7c3a5bcbb3f 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts @@ -13,3 +13,5 @@ export const METRIC_EXPLORER_AGGREGATIONS = [ 'count', 'sum', ] as const; + +export const CUSTOM_AGGREGATOR = 'custom'; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index bfbc685fa59be..4ffc270c89248 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -8,6 +8,7 @@ import * as rt from 'io-ts'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { TimeUnitChar } from '../utils/formatters/duration'; +import { CUSTOM_AGGREGATOR } from './constants'; type DeepPartialArray = Array>; @@ -84,12 +85,10 @@ export enum Aggregators { SUM = 'sum', MIN = 'min', MAX = 'max', - RATE = 'rate', CARDINALITY = 'cardinality', - P95 = 'p95', - P99 = 'p99', - CUSTOM = 'custom', } +export const aggType = fromEnum('Aggregators', Aggregators); +export type AggType = rt.TypeOf; export enum MetricsExplorerChartType { line = 'line', @@ -97,11 +96,6 @@ export enum MetricsExplorerChartType { bar = 'bar', } -export enum InfraRuleType { - MetricThreshold = 'metrics.alert.threshold', - InventoryThreshold = 'metrics.alert.inventory.threshold', -} - export enum AlertStates { OK, ALERT, @@ -132,38 +126,21 @@ export interface BaseMetricExpressionParams { warningThreshold?: number[]; } -export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Exclude; - metric: string; -} - -export interface CountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.COUNT; -} - -export type CustomMetricAggTypes = Exclude< - Aggregators, - Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 ->; - export interface CustomThresholdExpressionMetric { name: string; - aggType: CustomMetricAggTypes; + aggType: AggType; field?: string; filter?: string; } export interface CustomMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.CUSTOM; + aggType: typeof CUSTOM_AGGREGATOR; metrics: CustomThresholdExpressionMetric[]; equation?: string; label?: string; } -export type MetricExpressionParams = - | NonCountMetricExpressionParams - | CountMetricExpressionParams - | CustomMetricExpressionParams; +export type MetricExpressionParams = CustomMetricExpressionParams; export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID'); @@ -181,3 +158,23 @@ export enum InfraFormatterType { bits = 'bits', percent = 'percent', } + +/* + * Utils + * + * This utility function can be used to turn a TypeScript enum into a io-ts codec. + */ +export function fromEnum( + enumName: string, + theEnum: Record +): rt.Type { + const isEnumValue = (input: unknown): input is EnumType => + Object.values(theEnum).includes(input); + + return new rt.Type( + enumName, + isEnumValue, + (input, context) => (isEnumValue(input) ? rt.success(input) : rt.failure(input, context)), + rt.identity + ); +} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx index cc7422215551e..e4580ee6dc68f 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -14,6 +14,7 @@ import { Comparator, CustomMetricExpressionParams, } from '../../../../../common/custom_threshold_rule/types'; +import { CUSTOM_AGGREGATOR } from '../../../../../common/custom_threshold_rule/constants'; import { TimeUnitChar } from '../../../../../common'; import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; @@ -91,7 +92,7 @@ export const CustomEquationEditorWithFieldError = CustomEquationEditorTemplate.b const BASE_ARGS: Partial = { expression: { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, metrics: [ { name: 'A', @@ -119,7 +120,7 @@ CustomEquationEditorDefault.args = { CustomEquationEditorWithEquationErrors.args = { ...BASE_ARGS, expression: { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, equation: 'Math.round(A / B)', metrics: [ { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.user.pct' }, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index 835a5669fbb83..9cc71af2d3df6 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -17,15 +17,13 @@ import { EuiPopover, } from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; -import { omit, range, first, xor, debounce } from 'lodash'; +import { range, first, xor, debounce } from 'lodash'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../types'; import { Aggregators, - CustomMetricAggTypes, CustomThresholdExpressionMetric, } from '../../../../../common/custom_threshold_rule/types'; @@ -46,7 +44,7 @@ export interface CustomEquationEditorProps { const NEW_METRIC = { name: 'A', - aggType: Aggregators.COUNT as CustomMetricAggTypes, + aggType: Aggregators.COUNT as Aggregators, }; const MAX_VARIABLES = 26; const CHAR_CODE_FOR_A = 65; @@ -112,14 +110,12 @@ export function CustomEquationEditor({ const disableAdd = customMetrics?.length === MAX_VARIABLES; const disableDelete = customMetrics?.length === 1; - const filteredAggregationTypes = omit(aggregationTypes, OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS); - const metricRows = customMetrics?.map((row) => ( { it('should display no data message', async () => { const expression: MetricExpression = { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, metrics: [ { name: 'A', diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx index f0c86bc1430c1..8b0a4de92bd7e 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx @@ -9,6 +9,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; +import { CUSTOM_AGGREGATOR } from '../../../../common/custom_threshold_rule/constants'; import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../types'; import { ExpressionRow } from './expression_row'; @@ -56,7 +57,7 @@ describe('ExpressionRow', () => { it('should display thresholds as a percentage for pct metrics', async () => { const expression: MetricExpression = { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -82,7 +83,7 @@ describe('ExpressionRow', () => { it('should display thresholds as a decimal for all other metrics', async () => { const expression = { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index ee6dcbf44d4c0..e35eab6ead106 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -24,8 +24,8 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { debounce } from 'lodash'; -import { Comparator } from '../../../../common/custom_threshold_rule/types'; -import { AGGREGATION_TYPES, MetricExpression } from '../types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; +import { MetricExpression } from '../types'; import { CustomEquationEditor } from './custom_equation'; import { CUSTOM_EQUATION, LABEL_HELP_MESSAGE, LABEL_LABEL } from '../i18n_strings'; import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert'; @@ -236,7 +236,7 @@ export const aggregationType: { [key: string]: AggregationType } = { ), fieldRequired: true, validNormalizedTypes: ['number', 'histogram'], - value: AGGREGATION_TYPES.AVERAGE, + value: Aggregators.AVERAGE, }, max: { text: i18n.translate( @@ -247,7 +247,7 @@ export const aggregationType: { [key: string]: AggregationType } = { ), fieldRequired: true, validNormalizedTypes: ['number', 'date', 'histogram'], - value: AGGREGATION_TYPES.MAX, + value: Aggregators.MAX, }, min: { text: i18n.translate( @@ -258,7 +258,7 @@ export const aggregationType: { [key: string]: AggregationType } = { ), fieldRequired: true, validNormalizedTypes: ['number', 'date', 'histogram'], - value: AGGREGATION_TYPES.MIN, + value: Aggregators.MIN, }, cardinality: { text: i18n.translate( @@ -268,7 +268,7 @@ export const aggregationType: { [key: string]: AggregationType } = { } ), fieldRequired: false, - value: AGGREGATION_TYPES.CARDINALITY, + value: Aggregators.CARDINALITY, validNormalizedTypes: ['number', 'string', 'ip', 'date'], }, count: { @@ -279,7 +279,7 @@ export const aggregationType: { [key: string]: AggregationType } = { } ), fieldRequired: false, - value: AGGREGATION_TYPES.COUNT, + value: Aggregators.COUNT, validNormalizedTypes: ['number'], }, sum: { @@ -290,13 +290,7 @@ export const aggregationType: { [key: string]: AggregationType } = { } ), fieldRequired: false, - value: AGGREGATION_TYPES.SUM, - validNormalizedTypes: ['number', 'histogram'], - }, - custom: { - text: CUSTOM_EQUATION, - fieldRequired: false, - value: AGGREGATION_TYPES.CUSTOM, + value: Aggregators.SUM, validNormalizedTypes: ['number', 'histogram'], }, }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx index 23870a92ad981..199f5357fbea4 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/group_by.tsx @@ -26,7 +26,6 @@ interface Props { } export function GroupBy({ options, onChange, fields, errorOptions, ...rest }: Props) { - console.log('options:', options); const handleChange = useCallback( (selectedOptions: Array<{ label: string }>) => { const groupBy = selectedOptions.map((option) => option.label); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx index 3d737bdd3b861..330d1c931cd09 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -36,6 +36,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../utils/kibana_react'; +import { CUSTOM_AGGREGATOR } from '../../../common/custom_threshold_rule/constants'; import { Aggregators, Comparator } from '../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../common/utils/formatters/duration'; import { AlertContextMeta, AlertParams, MetricExpression } from './types'; @@ -50,8 +51,8 @@ type Props = Omit< 'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' >; -export const defaultExpression = { - aggType: Aggregators.CUSTOM, +export const defaultExpression: MetricExpression = { + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -62,7 +63,7 @@ export const defaultExpression = { threshold: [1000], timeSize: 1, timeUnit: 'm', -} as MetricExpression; +}; // eslint-disable-next-line import/no-default-export export default function Expressions(props: Props) { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts index 8d8832c4c251a..1f135473c0866 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_expression_chart_data.ts @@ -8,7 +8,11 @@ import DateMath from '@kbn/datemath'; import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; -import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; +import { + Aggregators, + AggType, + CustomThresholdExpressionMetric, +} from '../../../../common/custom_threshold_rule/types'; import { ExpressionOptions, ExpressionTimestampsRT, @@ -48,7 +52,7 @@ export const useExpressionChartData = ( equation: expression.equation, }, ], - aggregation: expression.aggType || 'avg', + aggregation: expression.aggType || 'custom', }), // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -82,14 +86,14 @@ const mapCustomThresholdMetricToMetricsExplorerMetric = ( if (metric.aggType === 'count') { return { name: metric.name, - aggregation: 'count', + aggregation: Aggregators.COUNT, filter: metric.filter, }; } return { name: metric.name, - aggregation: metric.aggType, + aggregation: metric.aggType as AggType, field: metric.field, }; }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 4790a03a8135f..f93bac2b0f104 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -6,6 +6,7 @@ */ import { v4 as uuidv4 } from 'uuid'; +import { CUSTOM_AGGREGATOR } from '../../../../common/custom_threshold_rule/constants'; import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { CustomThresholdAlert, CustomThresholdRule } from '../components/alert_details_app_section'; @@ -59,7 +60,7 @@ export const buildCustomThresholdRule = ( params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -72,7 +73,7 @@ export const buildCustomThresholdRule = ( timeUnit: 'm', }, { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -88,7 +89,7 @@ export const buildCustomThresholdRule = ( warningThreshold: [2.2], }, { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -154,7 +155,7 @@ export const buildCustomThresholdAlert = ( 'kibana.alert.rule.parameters': { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -168,7 +169,7 @@ export const buildCustomThresholdAlert = ( timeUnit: 'm', }, { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { diff --git a/x-pack/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts index 3fd027d97065e..e726a8fd01327 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -27,6 +27,7 @@ import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { CustomMetricExpressionParams, BaseMetricExpressionParams, + aggType, } from '../../../common/custom_threshold_rule/types'; import { ObservabilityPublicStart } from '../../plugin'; @@ -39,33 +40,6 @@ export type MetricExpression = Omit https://github.com/elastic/kibana/issues/159340 - // ml: MlPluginStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; osquery?: unknown; // OsqueryPluginStart; @@ -127,14 +99,6 @@ export type ExpressionTimestampsRT = rt.TypeOf; /* * Expression options */ -const aggType = rt.union([ - rt.literal('count'), - rt.literal('avg'), - rt.literal('sum'), - rt.literal('min'), - rt.literal('max'), - rt.literal('cardinality'), -]); export const metricsExplorerMetricRT = rt.intersection([ rt.type({ name: rt.string, @@ -170,7 +134,6 @@ export const expressionOptionsRT = rt.intersection([ }), ]); -export type AggType = rt.TypeOf; export type MetricsExplorerMetricRT = rt.TypeOf; export type ExpressionOptions = rt.TypeOf; /* @@ -180,8 +143,6 @@ export type ExpressionOptions = rt.TypeOf; /* * Metrics explorer types */ -export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; - export const timeRangeRT = rt.type({ from: rt.number, to: rt.number, @@ -233,9 +194,7 @@ export const metricsExplorerResponseRT = rt.type({ }); export type MetricsExplorerRow = rt.TypeOf; - export type MetricsExplorerSeries = rt.TypeOf; - export type MetricsExplorerResponse = rt.TypeOf; /* * End of metrics explorer types diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index e78d5e121558b..48903027299fc 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -21,6 +21,7 @@ import { CustomThresholdAlertContext } from './types'; import { Evaluation } from './lib/evaluate_rule'; import type { LogMeta, Logger } from '@kbn/logging'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; +import { CUSTOM_AGGREGATOR } from '../../../../common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -1193,136 +1194,6 @@ describe('The metric threshold alert type', () => { }); }); }); - describe('querying with the p99 aggregator', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - ...mockOptions.params, - criteria: [ - { - ...customThresholdNonCountCriterion, - comparator, - threshold, - aggType: Aggregators.P99, - metrics: [ - { - aggType: Aggregators.AVERAGE, - name: 'A', - field: 'test.metric.2', - }, - ], - }, - ], - }, - }); - test('alerts based on the p99 values', async () => { - setEvaluationResults([ - { - '*': { - ...customThresholdNonCountCriterion, - comparator: Comparator.GT, - threshold: [1], - metrics: [ - { - aggType: Aggregators.AVERAGE, - name: 'A', - field: 'test.metric.2', - }, - ], - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: true, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.GT, [1]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setEvaluationResults([ - { - '*': { - ...customThresholdNonCountCriterion, - comparator: Comparator.LT, - threshold: [1], - metrics: [ - { - aggType: Aggregators.AVERAGE, - name: 'A', - field: 'test.metric.2', - }, - ], - currentValue: 3, - timestamp: new Date().toISOString(), - shouldFire: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.LT, [1]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - }); - describe('querying with the p95 aggregator', () => { - afterAll(() => clearInstances()); - const instanceID = '*'; - const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => - executor({ - ...mockOptions, - services, - params: { - ...mockOptions.params, - sourceId, - criteria: [ - { - ...customThresholdNonCountCriterion, - comparator, - threshold, - aggType: Aggregators.P95, - }, - ], - }, - }); - test('alerts based on the p95 values', async () => { - setEvaluationResults([ - { - '*': { - ...customThresholdNonCountCriterion, - comparator: Comparator.GT, - threshold: [0.25], - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: true, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.GT, [0.25]); - expect(mostRecentAction(instanceID)).toBeAlertAction(); - setEvaluationResults([ - { - '*': { - ...customThresholdNonCountCriterion, - comparator: Comparator.LT, - threshold: [0.95], - currentValue: 1.0, - timestamp: new Date().toISOString(), - shouldFire: false, - isNoData: false, - bucketKey: { groupBy0: '*' }, - }, - }, - ]); - await execute(Comparator.LT, [0.95]); - expect(mostRecentAction(instanceID)).toBe(undefined); - }); - }); describe("querying a metric that hasn't reported data", () => { afterAll(() => clearInstances()); const instanceID = '*'; @@ -2011,7 +1882,7 @@ declare global { } const customThresholdNonCountCriterion: CustomMetricExpressionParams = { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { @@ -2026,7 +1897,7 @@ const customThresholdNonCountCriterion: CustomMetricExpressionParams = { }; const customThresholdCountCriterion: CustomMetricExpressionParams = { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, metrics: [ { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts deleted file mode 100644 index 73db4a3e747ee..0000000000000 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts +++ /dev/null @@ -1,24 +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 { Aggregators } from '../../../../../common/custom_threshold_rule/types'; - -export const createPercentileAggregation = ( - type: Aggregators.P95 | Aggregators.P99, - field: string -) => { - const value = type === Aggregators.P95 ? 95 : 99; - return { - aggregatedValue: { - percentiles: { - field, - percents: [value], - keyed: true, - }, - }, - }; -}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts index 41109fbeae912..c82f07594bf83 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; import moment from 'moment'; import { createTimerange } from './create_timerange'; @@ -18,65 +17,37 @@ describe('createTimerange(interval, aggType, timeframe)', () => { }; describe('Basic Metric Aggs', () => { it('should return a second range for last 1 second', () => { - const subject = createTimerange(1000, Aggregators.COUNT, timeframe); + const subject = createTimerange(1000, timeframe); expect(subject.end - subject.start).toEqual(1000); }); it('should return a minute range for last 1 minute', () => { - const subject = createTimerange(60000, Aggregators.COUNT, timeframe); + const subject = createTimerange(60000, timeframe); expect(subject.end - subject.start).toEqual(60000); }); it('should return 5 minute range for last 5 minutes', () => { - const subject = createTimerange(300000, Aggregators.COUNT, timeframe); + const subject = createTimerange(300000, timeframe); expect(subject.end - subject.start).toEqual(300000); }); it('should return a hour range for last 1 hour', () => { - const subject = createTimerange(3600000, Aggregators.COUNT, timeframe); + const subject = createTimerange(3600000, timeframe); expect(subject.end - subject.start).toEqual(3600000); }); it('should return a day range for last 1 day', () => { - const subject = createTimerange(86400000, Aggregators.COUNT, timeframe); + const subject = createTimerange(86400000, timeframe); expect(subject.end - subject.start).toEqual(86400000); }); }); - describe('Rate Aggs', () => { - it('should return a 20 second range for last 1 second', () => { - const subject = createTimerange(1000, Aggregators.RATE, timeframe); - expect(subject.end - subject.start).toEqual(1000 * 2); - }); - it('should return a 5 minute range for last 1 minute', () => { - const subject = createTimerange(60000, Aggregators.RATE, timeframe); - expect(subject.end - subject.start).toEqual(60000 * 2); - }); - it('should return 25 minute range for last 5 minutes', () => { - const subject = createTimerange(300000, Aggregators.RATE, timeframe); - expect(subject.end - subject.start).toEqual(300000 * 2); - }); - it('should return 5 hour range for last hour', () => { - const subject = createTimerange(3600000, Aggregators.RATE, timeframe); - expect(subject.end - subject.start).toEqual(3600000 * 2); - }); - it('should return a 5 day range for last day', () => { - const subject = createTimerange(86400000, Aggregators.RATE, timeframe); - expect(subject.end - subject.start).toEqual(86400000 * 2); - }); - }); describe('With lastPeriodEnd', () => { it('should return a minute and 1 second range for last 1 second when the lastPeriodEnd is less than the timeframe start', () => { const subject = createTimerange( 1000, - Aggregators.COUNT, timeframe, end.clone().subtract(1, 'minutes').valueOf() ); expect(subject.end - subject.start).toEqual(61000); }); it('should return a second range for last 1 second when the lastPeriodEnd is not less than the timeframe start', () => { - const subject = createTimerange( - 1000, - Aggregators.COUNT, - timeframe, - end.clone().add(2, 'seconds').valueOf() - ); + const subject = createTimerange(1000, timeframe, end.clone().add(2, 'seconds').valueOf()); expect(subject.end - subject.start).toEqual(1000); }); }); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts index 257318a0bd109..a6c39adcd3204 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts @@ -6,20 +6,16 @@ */ import moment from 'moment'; -import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; export const createTimerange = ( interval: number, - aggType: Aggregators, timeframe: { end: string; start: string }, lastPeriodEnd?: number ) => { const end = moment(timeframe.end).valueOf(); let start = moment(timeframe.start).valueOf(); - // Rate aggregations need 5 buckets worth of data - const minimumBuckets = aggType === Aggregators.RATE ? 2 : 1; - start = start - interval * minimumBuckets; + start = start - interval; // Use lastPeriodEnd - interval when it's less than start if (lastPeriodEnd && lastPeriodEnd - interval < start) { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index d90c6ba0caef3..b1f790da9d146 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -49,12 +49,7 @@ export const evaluateRule = async { @@ -22,7 +23,7 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { field: 'system.is.a.good.puppy.dog', }, ], - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, timeUnit: 'm', timeSize: 1, threshold: [1], diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index f3d2b1f6c9c94..2166c21db8609 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -8,6 +8,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { extractReferences, injectReferences } from '@kbn/data-plugin/common'; +import { dataViewSpecSchema } from '@kbn/data-views-plugin/server/rest_api_routes/schema'; import { i18n } from '@kbn/i18n'; import { IRuleTypeAlerts, GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server'; import { IBasePath, Logger } from '@kbn/core/server'; @@ -52,7 +53,7 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { }; export const searchConfigurationSchema = schema.object({ - index: schema.string(), + index: schema.oneOf([schema.string(), dataViewSpecSchema]), query: schema.object({ language: schema.string({ validate: validateKQLStringFilter, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts index 693277bd3af2a..97881f55d8d32 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts @@ -18,8 +18,6 @@ import { FIRED_ACTIONS_ID, NO_DATA_ACTIONS_ID, FIRED_ACTION, NO_DATA_ACTION } fr import { MissingGroupsRecord } from './lib/check_missing_group'; import { AdditionalContext } from './utils'; import { searchConfigurationSchema } from './register_custom_threshold_rule_type'; -import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; -import { TimeUnitChar } from '../../../../common'; export enum AlertStates { OK, @@ -75,27 +73,6 @@ type CustomThresholdAlert = Alert< CustomThresholdSpecificActionGroups >; -interface BaseMetricExpressionParams { - timeSize: number; - timeUnit: TimeUnitChar; - threshold: number[]; - comparator: Comparator; -} - -export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Exclude; - metric: string; -} - -export interface CountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Aggregators.COUNT; -} - -export type CustomMetricAggTypes = Exclude< - Aggregators, - Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 ->; - export interface AlertExecutionDetails { alertId: string; executionId: string; diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts index 4c67a7fcb3495..ca2772dbd10db 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; import { @@ -90,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.5], timeSize: 5, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts index d984e01fead57..d54c5109b55ff 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import moment from 'moment'; import { Aggregators, @@ -83,7 +84,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.5], timeSize: 5, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts index 4e2f547828f60..b11e0233ffba9 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import moment from 'moment'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { format } from 'url'; @@ -96,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [7500000], timeSize: 5, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 7446b4b8b6ea3..71f0a1bed860b 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -11,6 +11,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; import { @@ -96,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.9], timeSize: 1, @@ -194,7 +195,7 @@ export default function ({ getService }: FtrProviderContext) { .eql({ criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.9], timeSize: 1, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts index db18cd4e899ed..90e7ca58dafbc 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; import { @@ -90,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [2], timeSize: 1, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts index 20d25ecb4db96..74144e8e7c721 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -7,6 +7,7 @@ import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -90,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT_OR_EQ, threshold: [0.2], timeSize: 1, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts index ada064c133ffc..0cbb1044f887e 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -73,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [7500000], timeSize: 5, diff --git a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts index 4bde235e97dd2..d56b91dda5515 100644 --- a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts +++ b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import type { SuperTest, Test } from 'supertest'; @@ -32,7 +31,7 @@ export async function createIndexConnector({ return body.id as string; } -export async function createRule({ +export async function createRule({ supertest, name, ruleTypeId, @@ -45,7 +44,7 @@ export async function createRule({ supertest: SuperTest; ruleTypeId: string; name: string; - params: MetricThresholdParams | ThresholdParams; + params: Params; actions?: any[]; tags?: any[]; schedule?: { interval: string }; diff --git a/x-pack/test/alerting_api_integration/observability/metric_threshold_rule.ts b/x-pack/test/alerting_api_integration/observability/metric_threshold_rule.ts index 0de7e3d600612..181b34c3956c1 100644 --- a/x-pack/test/alerting_api_integration/observability/metric_threshold_rule.ts +++ b/x-pack/test/alerting_api_integration/observability/metric_threshold_rule.ts @@ -8,7 +8,12 @@ import moment from 'moment'; import expect from '@kbn/expect'; import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator, InfraRuleType } from '@kbn/infra-plugin/common/alerting/metrics'; +import { + Aggregators, + Comparator, + InfraRuleType, + MetricThresholdParams, +} from '@kbn/infra-plugin/common/alerting/metrics'; import { waitForDocumentInIndex, waitForAlertInIndex, @@ -48,7 +53,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'Index Connector: Metric threshold API test', indexName: ALERT_ACTION_INDEX, }); - const createdRule = await createRule({ + const createdRule = await createRule({ supertest, ruleTypeId: InfraRuleType.MetricThreshold, consumer: 'infrastructure', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts index 4aea7f5d670db..2b7fc3653c3b6 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -6,6 +6,7 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -88,7 +89,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.5], timeSize: 5, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts index 2d6e8d63afbf2..4f1e9a4ab080b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -74,7 +75,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.5], timeSize: 5, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 1f02377919a8d..e22417115befe 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -12,6 +12,7 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -90,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.9], timeSize: 1, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts index 5c34258de8c15..0f47627be5e2b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -6,6 +6,7 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -84,7 +85,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [2], timeSize: 1, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index 864b35de6f3d4..78daabb1eb131 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -11,6 +11,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { kbnTestConfig } from '@kbn/test'; import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; @@ -94,7 +95,7 @@ export default function ({ getService }: FtrProviderContext) { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT_OR_EQ, threshold: [0.2], timeSize: 1, From a298924d924a958441557656a2fe29e6d07d320d Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Thu, 2 Nov 2023 15:52:23 +0100 Subject: [PATCH 13/17] Remove more types --- .../common/custom_threshold_rule/types.ts | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index 4ffc270c89248..40d569152d1a3 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -10,15 +10,6 @@ import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { TimeUnitChar } from '../utils/formatters/duration'; import { CUSTOM_AGGREGATOR } from './constants'; -type DeepPartialArray = Array>; - -type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; -export type DeepPartial = T extends any[] - ? DeepPartialArray - : T extends object - ? DeepPartialObject - : T; - export const ThresholdFormatterTypeRT = rt.keyof({ abbreviatedNumber: null, bits: null, @@ -29,47 +20,6 @@ export const ThresholdFormatterTypeRT = rt.keyof({ }); export type ThresholdFormatterType = rt.TypeOf; -/** - * Properties specific to the Metrics Source Configuration. - */ -export const SourceConfigurationTimestampColumnRuntimeType = rt.type({ - timestampColumn: rt.type({ - id: rt.string, - }), -}); -export const SourceConfigurationMessageColumnRuntimeType = rt.type({ - messageColumn: rt.type({ - id: rt.string, - }), -}); - -export const SourceConfigurationFieldColumnRuntimeType = rt.type({ - fieldColumn: rt.type({ - id: rt.string, - field: rt.string, - }), -}); - -export const SourceConfigurationColumnRuntimeType = rt.union([ - SourceConfigurationTimestampColumnRuntimeType, - SourceConfigurationMessageColumnRuntimeType, - SourceConfigurationFieldColumnRuntimeType, -]); - -// Kibana data views -export const logDataViewReferenceRT = rt.type({ - type: rt.literal('data_view'), - dataViewId: rt.string, -}); - -// Index name -export const logIndexNameReferenceRT = rt.type({ - type: rt.literal('index_name'), - indexName: rt.string, -}); - -export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); - export enum Comparator { GT = '>', LT = '<', From 5ef6a4c89534fce6b93673b38df33d32c02892eb Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Thu, 2 Nov 2023 17:40:18 +0100 Subject: [PATCH 14/17] Remove usage of Aggregators.CUSTOM --- .../alerting_test_data/src/create_custom_threshold_rule.ts | 3 ++- .../src/scenarios/custom_threshold_log_count.ts | 3 ++- .../src/scenarios/custom_threshold_log_count_groupby.ts | 3 ++- .../src/scenarios/custom_threshold_log_count_nodata.ts | 3 ++- .../src/scenarios/custom_threshold_metric_avg.ts | 3 ++- .../src/scenarios/custom_threshold_metric_avg_groupby.ts | 3 ++- .../src/scenarios/custom_threshold_metric_avg_nodata.ts | 3 ++- .../custom_threshold_rule/custom_eq_avg_bytes_fired.ts | 2 +- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts b/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts index 296d1b95512d3..9c7e1fb6acdb7 100644 --- a/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts +++ b/x-pack/packages/observability/alerting_test_data/src/create_custom_threshold_rule.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -38,7 +39,7 @@ export const createCustomThresholdRule = async ( params: { criteria: ruleParams.params?.criteria || [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [1], timeSize: 1, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts index 39b7159ff478b..c2515cfe1615a 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario1 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.LT, threshold: [100], timeSize: 1, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts index 882d83bbbf973..68786ea4a06d2 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_groupby.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario2 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.LT, threshold: [40], timeSize: 1, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts index 7be8b142380d1..97585a66b8385 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_log_count_nodata.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario3 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.LT, threshold: [5], timeSize: 1, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts index 755f89680b171..0feec99ca5a47 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario4 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [80], timeSize: 1, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts index a662608ab31d6..a36bb4af2544c 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_groupby.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario5 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [80], timeSize: 5, diff --git a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts index 5e98aa1a58d31..16639c7749ca3 100644 --- a/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts +++ b/x-pack/packages/observability/alerting_test_data/src/scenarios/custom_threshold_metric_avg_nodata.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CUSTOM_AGGREGATOR } from '@kbn/observability-plugin/common/custom_threshold_rule/constants'; import { Aggregators, Comparator, @@ -22,7 +23,7 @@ export const scenario6 = { params: { criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.LT, threshold: [1], timeSize: 1, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index e22417115befe..47ed54412296d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -188,7 +188,7 @@ export default function ({ getService }: FtrProviderContext) { .eql({ criteria: [ { - aggType: Aggregators.CUSTOM, + aggType: CUSTOM_AGGREGATOR, comparator: Comparator.GT, threshold: [0.9], timeSize: 1, From 6debb6f1ee379ef0dbc8a4930ad014a6baac544a Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 8 Nov 2023 12:00:55 +0100 Subject: [PATCH 15/17] Remove comment --- .../lib/rules/custom_threshold/custom_threshold_executor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 526cc828384bb..2e50c33048f51 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -86,7 +86,6 @@ export const createCustomThresholdExecutor = ({ executionId, }); - // TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340 const { alertWithLifecycle, getAlertUuid, From d630f205fd08779e6913f74e9d4bd5e0d5eac799 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 8 Nov 2023 12:41:42 +0100 Subject: [PATCH 16/17] Fix translation --- x-pack/plugins/translations/translations/fr-FR.json | 7 ------- x-pack/plugins/translations/translations/ja-JP.json | 7 ------- x-pack/plugins/translations/translations/zh-CN.json | 7 ------- 3 files changed, 21 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c06aff873c9bf..8323bf1825446 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -29107,8 +29107,6 @@ "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabel": "Dernière {lookback} {timeLabel}", "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabelWithGrouping": "Dernières {lookback} {timeLabel} de données pour {id}", "xpack.observability.customThreshold.rule.threshold.errorAlertReason": "Elasticsearch a échoué lors de l'interrogation des données pour {metric}", - "xpack.observability.customThreshold.rule.threshold.noDataAlertReason": "{metric} n'a signalé aucune donnée dans les dernières {interval} {group}", - "xpack.observability.customThreshold.rule.threshold.recoveredAlertReason": "{metric} est maintenant {comparator} un seuil de {threshold} (la valeur actuelle est {currentValue}) pour {group}", "xpack.observability.customThreshold.rule.threshold.thresholdRange": "{a} et {b}", "xpack.observability.customThreshold.rule.thresholdExtraTitle": "Alerte lorsque {comparator} {threshold}.", "xpack.observability.enableAgentExplorerDescription": "{betaLabel} Active la vue d'explorateur d'agent.", @@ -29257,9 +29255,6 @@ "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.count": "Compte du document", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.max": "Max", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.min": "Min", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p95": "95e centile", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p99": "99e centile", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.rate": "Taux", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.sum": "Somme", "xpack.observability.customThreshold.rule.alertFlyout.alertDescription": "Alerter quand un type de données Observability atteint ou dépasse une valeur donnée.", "xpack.observability.customThreshold.rule.alertFlyout.alertOnGroupDisappear": "Me prévenir si un groupe cesse de signaler les données", @@ -29280,12 +29275,10 @@ "xpack.observability.customThreshold.rule.alertFlyout.customEquationTooltip": "Ceci est compatible avec des calculs de base (A + B / C) et la logique booléenne (A < B ? A : B).", "xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp": "La vue de données sélectionnée ne dispose pas de champ d'horodatage. Veuillez sélectionner une autre vue de données.", "xpack.observability.customThreshold.rule.alertFlyout.defineTextQueryPrompt": "Définir le filtre de recherche (facultatif)", - "xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[Ce paramètre n’est pas applicable à l’agrégateur du nombre de documents.]", "xpack.observability.customThreshold.rule.alertFlyout.error.aggregationRequired": "L'agrégation est requise.", "xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters": "Le champ d'équation prend en charge uniquement les caractères suivants : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "La requête de filtre n'est pas valide.", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "La vue de données est requise.", - "xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired": "L'indicateur est requis.", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "L'agrégation est requise", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "Le champ est obligatoire", "xpack.observability.customThreshold.rule.alertFlyout.error.metricsError": "Vous devez définir au moins 1 indicateur personnalisé", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f27f5b60da13..42339830163a3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -29106,8 +29106,6 @@ "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabel": "最後の{lookback} {timeLabel}", "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id}のデータの最後の{lookback} {timeLabel}", "xpack.observability.customThreshold.rule.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました", - "xpack.observability.customThreshold.rule.threshold.noDataAlertReason": "{metric}は最後の{interval}{group}でデータがないことを報告しました", - "xpack.observability.customThreshold.rule.threshold.recoveredAlertReason": "{metric} が {comparator} に、{group} が {threshold} のしきい値(現在の値は {currentValue})になりました", "xpack.observability.customThreshold.rule.threshold.thresholdRange": "{a}および{b}", "xpack.observability.customThreshold.rule.thresholdExtraTitle": "{comparator} {threshold}のときにアラートを通知", "xpack.observability.enableAgentExplorerDescription": "{betaLabel}エージェントエクスプローラー表示を有効にします。", @@ -29256,9 +29254,6 @@ "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.count": "ドキュメントカウント", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.max": "最高", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.min": "最低", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p95": "95パーセンタイル", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p99": "99パーセンタイル", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.rate": "レート", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.sum": "合計", "xpack.observability.customThreshold.rule.alertFlyout.alertDescription": "オブザーバビリティデータタイプが特定の値以上になったときにアラートを送信します。", "xpack.observability.customThreshold.rule.alertFlyout.alertOnGroupDisappear": "グループがデータのレポートを停止する場合にアラートで通知する", @@ -29279,12 +29274,10 @@ "xpack.observability.customThreshold.rule.alertFlyout.customEquationTooltip": "これは基本的な数学ロジック(A + B / C)とブールロジック(A < B ?A :B)をサポートします。", "xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp": "選択したデータビューにタイムスタンプフィールドがありません。他のデータビューを選択してください。", "xpack.observability.customThreshold.rule.alertFlyout.defineTextQueryPrompt": "クエリフィルターを定義(任意)", - "xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[この設定は、ドキュメントカウントアグリゲーターには適用されません。]", "xpack.observability.customThreshold.rule.alertFlyout.error.aggregationRequired": "集約が必要です。", "xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters": "等式フィールドでは次の文字のみを使用できます:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "フィルタークエリは無効です。", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "データビューが必要です。", - "xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired": "メトリックが必要です。", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "集約が必要です", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "フィールドが必要です", "xpack.observability.customThreshold.rule.alertFlyout.error.metricsError": "1つ以上のカスタムメトリックを定義する必要があります", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7c8328a39f987..f04a94a53e724 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -29104,8 +29104,6 @@ "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabel": "过去 {lookback} {timeLabel}", "xpack.observability.customThreshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id} 过去 {lookback} {timeLabel}的数据", "xpack.observability.customThreshold.rule.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障", - "xpack.observability.customThreshold.rule.threshold.noDataAlertReason": "对于 {group},{metric} 在过去 {interval}中未报告数据", - "xpack.observability.customThreshold.rule.threshold.recoveredAlertReason": "对于 {group},{metric} 现在{comparator}阈值 {threshold}(当前值为 {currentValue})", "xpack.observability.customThreshold.rule.threshold.thresholdRange": "{a} 和 {b}", "xpack.observability.customThreshold.rule.thresholdExtraTitle": "{comparator} {threshold} 时告警", "xpack.observability.enableAgentExplorerDescription": "{betaLabel} 启用代理浏览器视图。", @@ -29254,9 +29252,6 @@ "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.count": "文档计数", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.max": "最大值", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.min": "最小值", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p95": "第 95 个百分位", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p99": "第 99 个百分位", - "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.rate": "比率", "xpack.observability.customThreshold.rule.alertFlyout.aggregationText.sum": "求和", "xpack.observability.customThreshold.rule.alertFlyout.alertDescription": "任何 Observability 数据类型到达或超出给定值时告警。", "xpack.observability.customThreshold.rule.alertFlyout.alertOnGroupDisappear": "组停止报告数据时提醒我", @@ -29277,12 +29272,10 @@ "xpack.observability.customThreshold.rule.alertFlyout.customEquationTooltip": "这支持基本数学 (A + B / C) 和布尔逻辑 (A < B ?A :B)。", "xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp": "选定数据视图没有时间戳字段,请选择其他数据视图。", "xpack.observability.customThreshold.rule.alertFlyout.defineTextQueryPrompt": "定义查询筛选(可选)", - "xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[此设置不适用于文档计数聚合器。]", "xpack.observability.customThreshold.rule.alertFlyout.error.aggregationRequired": "“聚合”必填。", "xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters": "方程字段仅支持以下字符:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "筛选查询无效。", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "需要数据视图。", - "xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired": "“指标”必填。", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "“聚合”必填", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "“字段”必填", "xpack.observability.customThreshold.rule.alertFlyout.error.metricsError": "必须至少定义 1 个定制指标", From 261685cd574449d3688c82c597fa65f632a5c9de Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 13 Nov 2023 14:11:17 +0100 Subject: [PATCH 17/17] Fix bug in changing aggs --- .../custom_equation_editor.tsx | 1 + .../custom_equation/metric_row_with_agg.tsx | 26 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index 55352232b7239..164db3bac1682 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -158,6 +158,7 @@ export function CustomEquationEditor({ { onChange({ name, - field, + field: customAggType === Aggregators.COUNT ? undefined : field, aggType: customAggType as Aggregators, }); },