diff --git a/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts b/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts index 9c7fc7ec9ecee..e14151b482dce 100644 --- a/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts +++ b/x-pack/packages/observability/alert_details/src/helpers/get_padded_alert_time_range.ts @@ -8,9 +8,8 @@ import moment from 'moment'; export interface TimeRange { - from?: string; - to?: string; - interval?: string; + from: string; + to: string; } export const getPaddedAlertTimeRange = (alertStart: string, alertEnd?: string): TimeRange => { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/logs_ratio_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/logs_ratio_chart.tsx deleted file mode 100644 index 08957bdd4cd69..0000000000000 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/logs_ratio_chart.tsx +++ /dev/null @@ -1,238 +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 { - Chart, - BarSeries, - ScaleType, - LineAnnotation, - RectAnnotation, - Axis, - Settings, - Position, - AnnotationDomainType, - Tooltip, -} from '@elastic/charts'; -import React, { ReactElement, useEffect, useMemo } from 'react'; -import { useIsDarkMode } from '../../../../../hooks/use_is_dark_mode'; -import { ExecutionTimeRange } from '../../../../../types'; -import { decodeOrThrow } from '../../../../../../common/runtime_types'; -import { - GetLogAlertsChartPreviewDataAlertParamsSubset, - getLogAlertsChartPreviewDataAlertParamsSubsetRT, -} from '../../../../../../common/http_api'; -import { - Comparator, - PartialRuleParams, - Threshold, -} from '../../../../../../common/alerting/logs/log_threshold'; -import { PersistedLogViewReference } from '../../../../../../common/log_views'; -import { useKibanaTimeZoneSetting } from '../../../../../hooks/use_kibana_time_zone_setting'; -import { getChartTheme } from '../../../../../utils/get_chart_theme'; -import { - yAxisFormatter, - tooltipProps, - getDomain, - useDateFormatter, - LoadingState, - ErrorState, - NoDataState, - ChartContainer, -} from '../../../../common/criterion_preview_chart/criterion_preview_chart'; -import { Color, colorTransformer } from '../../../../../../common/color_palette'; -import { useChartPreviewData } from '../../expression_editor/hooks/use_chart_preview_data'; - -interface ChartProps { - buckets: number; - logViewReference: PersistedLogViewReference; - ruleParams: PartialRuleParams; - threshold?: Threshold; - showThreshold: boolean; - executionTimeRange?: ExecutionTimeRange; - filterSeriesByGroupName?: string; - annotations?: Array>; -} - -const LogsRatioChart: React.FC = ({ - buckets, - ruleParams, - logViewReference, - threshold, - showThreshold, - executionTimeRange, - filterSeriesByGroupName, - annotations, -}) => { - const chartAlertParams: GetLogAlertsChartPreviewDataAlertParamsSubset | null = useMemo(() => { - const params = { - criteria: ruleParams.criteria, - count: { - comparator: ruleParams.count.comparator, - value: ruleParams.count.value, - }, - timeSize: ruleParams.timeSize, - timeUnit: ruleParams.timeUnit, - groupBy: ruleParams.groupBy, - }; - - try { - return decodeOrThrow(getLogAlertsChartPreviewDataAlertParamsSubsetRT)(params); - } catch (error) { - return null; - } - }, [ - ruleParams.criteria, - ruleParams.count.comparator, - ruleParams.count.value, - ruleParams.timeSize, - ruleParams.timeUnit, - ruleParams.groupBy, - ]); - const { - getChartPreviewData, - isLoading, - hasError, - chartPreviewData: series, - } = useChartPreviewData({ - logViewReference, - ruleParams: chartAlertParams, - buckets, - executionTimeRange, - filterSeriesByGroupName, - }); - - useEffect(() => { - getChartPreviewData(); - }, [getChartPreviewData]); - - const isDarkMode = useIsDarkMode(); - const timezone = useKibanaTimeZoneSetting(); - const { yMin, yMax, xMin, xMax } = getDomain(series, false); - const dateFormatter = useDateFormatter(xMin, xMax); - const hasData = series.length > 0; - const THRESHOLD_OPACITY = 0.3; - const chartDomain = { - max: showThreshold && threshold ? Math.max(yMax, threshold.value) * 1.1 : yMax * 1.1, // Add 10% headroom. - min: showThreshold && threshold ? Math.min(yMin, threshold.value) : yMin, - }; - const isAbove = - showThreshold && threshold && threshold.comparator - ? [Comparator.GT, Comparator.GT_OR_EQ].includes(threshold.comparator) - : false; - - const isBelow = - showThreshold && threshold && threshold.comparator - ? [Comparator.LT, Comparator.LT_OR_EQ].includes(threshold.comparator) - : false; - const barSeries = useMemo(() => { - return series.flatMap(({ points, id }) => points.map((point) => ({ ...point, groupBy: id }))); - }, [series]); - - if (isLoading) { - return ; - } else if (hasError) { - return ; - } else if (!hasData) { - return ; - } - return ( - - - - {showThreshold && threshold ? ( - - ) : null} - {showThreshold && threshold && isBelow ? ( - - ) : null} - {annotations} - {showThreshold && threshold && isAbove ? ( - - ) : null} - - - - - - - ); -}; -// eslint-disable-next-line import/no-default-export -export default LogsRatioChart; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/create_lens_definition.ts b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/create_lens_definition.ts new file mode 100644 index 0000000000000..ab3bc5f72bbfa --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/create_lens_definition.ts @@ -0,0 +1,395 @@ +/* + * 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 { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { EuiThemeComputed, transparentize } from '@elastic/eui'; + +export interface IndexPattern { + pattern: string; + timestampField: string; +} + +export interface Threshold { + value: number; + fill: 'above' | 'below'; +} + +export interface Timerange { + from: number; + to?: number; +} + +function createBaseLensDefinition( + index: IndexPattern, + euiTheme: EuiThemeComputed, + threshold: Threshold, + alertRange: Timerange, + layerDef: D, + filter?: string +) { + return { + title: 'Threshold Chart', + visualizationType: 'lnsXY', + type: 'lens', + references: [], + state: { + visualization: { + legend: { + isVisible: false, + position: 'right', + }, + valueLabels: 'hide', + fittingFunction: 'None', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: false, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: 'e6f553a0-9e36-4eea-8ecf-8261523c6f44', + accessors: ['607b2253-ed20-4f0a-bf62-07a1f846cca4'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: '8ed7d473-ff48-4c90-be2c-ae46f3a11030', + yConfig: [ + { + forAccessor: '607b2253-ed20-4f0a-bf62-07a1f846cca4', + color: '#6092c0', + }, + ], + }, + { + layerId: '62dfc313-3922-4870-b568-ff0818da38b3', + layerType: 'annotations', + annotations: [ + { + type: 'manual', + id: 'ffe44253-a8c7-4755-821f-47be5bfac288', + label: 'Alert Line', + key: { + type: 'point_in_time', + timestamp: moment(alertRange.from).toISOString(), + }, + lineWidth: 3, + color: euiTheme.colors.danger, + icon: 'alert', + }, + { + type: 'manual', + label: 'Alert', + key: { + type: 'range', + timestamp: moment(alertRange.from).toISOString(), + endTimestamp: moment(alertRange.to).toISOString(), + }, + id: '07d15b13-4b6c-4d82-b45d-9d58ced1c2a8', + color: transparentize(euiTheme.colors.danger, 0.2), + }, + ], + ignoreGlobalFilters: true, + persistanceType: 'byValue', + }, + { + layerId: '90f87c46-9685-49af-b4ed-066eb65e2b39', + layerType: 'referenceLine', + accessors: ['7fb02af1-0823-4787-a316-3b05a4539d2c'], + yConfig: [ + { + forAccessor: '7fb02af1-0823-4787-a316-3b05a4539d2c', + axisMode: 'left', + color: euiTheme.colors.danger, + lineWidth: 2, + fill: threshold.fill, + }, + ], + }, + ], + }, + query: { + query: filter || '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + 'e6f553a0-9e36-4eea-8ecf-8261523c6f44': layerDef, + '90f87c46-9685-49af-b4ed-066eb65e2b39': { + linkToLayers: [], + columns: { + '7fb02af1-0823-4787-a316-3b05a4539d2c': { + label: 'Threshold', + dataType: 'number', + operationType: 'static_value', + isStaticValue: true, + isBucketed: false, + scale: 'ratio', + params: { + value: threshold.value, + }, + references: [], + customLabel: true, + }, + }, + columnOrder: ['7fb02af1-0823-4787-a316-3b05a4539d2c'], + sampling: 1, + ignoreGlobalFilters: false, + incompleteColumns: {}, + }, + }, + }, + indexpattern: { + layers: {}, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: 'd09436e6-20c0-4982-aaf6-b67ec371b27d', + name: 'indexpattern-datasource-layer-e6f553a0-9e36-4eea-8ecf-8261523c6f44', + }, + { + type: 'index-pattern', + id: 'd09436e6-20c0-4982-aaf6-b67ec371b27d', + name: 'indexpattern-datasource-layer-90f87c46-9685-49af-b4ed-066eb65e2b39', + }, + { + type: 'index-pattern', + id: 'd09436e6-20c0-4982-aaf6-b67ec371b27d', + name: 'xy-visualization-layer-62dfc313-3922-4870-b568-ff0818da38b3', + }, + ], + adHocDataViews: { + 'd09436e6-20c0-4982-aaf6-b67ec371b27d': { + id: 'd09436e6-20c0-4982-aaf6-b67ec371b27d', + title: index.pattern, + timeFieldName: index.timestampField, + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: 'adhoc', + }, + }, + }, + }; +} + +export function createLensDefinitionForRatioChart( + index: IndexPattern, + euiTheme: EuiThemeComputed, + numeratorKql: string, + denominatorKql: string, + threshold: Threshold, + alertRange: Timerange, + interval: string, + filter?: string +) { + const layerDef = { + columns: { + '8ed7d473-ff48-4c90-be2c-ae46f3a11030': { + label: index.timestampField, + dataType: 'date', + operationType: 'date_histogram', + sourceField: index.timestampField, + isBucketed: true, + scale: 'interval', + params: { + interval, + includeEmptyRows: true, + dropPartials: false, + }, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0': { + label: 'Part of ratio', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: numeratorKql, + language: 'kuery', + }, + params: { + emptyAsNull: false, + }, + customLabel: true, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4X1': { + label: 'Part of ratio', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: denominatorKql, + language: 'kuery', + }, + params: { + emptyAsNull: false, + }, + customLabel: true, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4X2': { + label: 'Part of ratio', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + tinymathAst: { + type: 'function', + name: 'divide', + args: [ + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X1', + ], + location: { + min: 0, + max: 94, + }, + text: `count(kql=\'${numeratorKql}\') / count(kql=\'${denominatorKql}\')`, + }, + }, + references: [ + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X1', + ], + customLabel: true, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4': { + label: 'ratio', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + formula: `count(kql=\'${numeratorKql}\') / count(kql=\'${denominatorKql}\')`, + isFormulaBroken: false, + }, + references: ['607b2253-ed20-4f0a-bf62-07a1f846cca4X2'], + customLabel: true, + }, + }, + columnOrder: [ + '8ed7d473-ff48-4c90-be2c-ae46f3a11030', + '607b2253-ed20-4f0a-bf62-07a1f846cca4', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X1', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X2', + ], + incompleteColumns: {}, + sampling: 1, + }; + return createBaseLensDefinition( + index, + euiTheme, + threshold, + alertRange, + layerDef, + filter + ) as unknown as TypedLensByValueInput['attributes']; +} + +export function createLensDefinitionForCountChart( + index: IndexPattern, + euiTheme: EuiThemeComputed, + kql: string, + threshold: Threshold, + alertRange: Timerange, + interval: string, + filter?: string +) { + const layerDef = { + columns: { + '8ed7d473-ff48-4c90-be2c-ae46f3a11030': { + label: index.timestampField, + dataType: 'date', + operationType: 'date_histogram', + sourceField: index.timestampField, + isBucketed: true, + scale: 'interval', + params: { + interval, + includeEmptyRows: true, + dropPartials: false, + }, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0': { + label: `Part of count(kql=\'${kql}\')`, + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: kql, + language: 'kuery', + }, + params: { + emptyAsNull: false, + }, + customLabel: true, + }, + '607b2253-ed20-4f0a-bf62-07a1f846cca4': { + label: 'document count', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + formula: `count(kql=\'${kql}\')`, + isFormulaBroken: false, + }, + references: ['607b2253-ed20-4f0a-bf62-07a1f846cca4X0'], + customLabel: true, + }, + }, + columnOrder: [ + '8ed7d473-ff48-4c90-be2c-ae46f3a11030', + '607b2253-ed20-4f0a-bf62-07a1f846cca4', + '607b2253-ed20-4f0a-bf62-07a1f846cca4X0', + ], + incompleteColumns: {}, + sampling: 1, + }; + + return createBaseLensDefinition( + index, + euiTheme, + threshold, + alertRange, + layerDef, + filter + ) as unknown as TypedLensByValueInput['attributes']; +} diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/index.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/index.tsx new file mode 100644 index 0000000000000..578a3675a0d2e --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { LogThresholdCountChart } from './log_threshold_count_chart'; +export { LogThresholdRatioChart } from './log_threshold_ratio_chart'; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_count_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_count_chart.tsx new file mode 100644 index 0000000000000..4a474ea7f5ef1 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_count_chart.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; +import { + createLensDefinitionForCountChart, + IndexPattern, + Threshold, + Timerange, +} from './create_lens_definition'; + +interface LogThresholdCountChartProps { + index: IndexPattern; + threshold: Threshold; + timeRange: { from: string; to: string }; + alertRange: Timerange; + kql: string; + height: number; + interval?: string; + filter?: string; +} + +export function LogThresholdCountChart({ + kql, + index, + threshold, + timeRange, + alertRange, + height, + interval = 'auto', + filter = '', +}: LogThresholdCountChartProps) { + const { + lens: { EmbeddableComponent }, + } = useKibanaContextForPlugin().services; + const { euiTheme } = useEuiTheme(); + const lensDef = createLensDefinitionForCountChart( + index, + euiTheme, + kql, + threshold, + alertRange, + interval, + filter + ); + return ( +
+ +
+ ); +} diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_ratio_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_ratio_chart.tsx new file mode 100644 index 0000000000000..2e415b0e5468d --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/threhsold_chart/log_threshold_ratio_chart.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; +import { + createLensDefinitionForRatioChart, + IndexPattern, + Threshold, + Timerange, +} from './create_lens_definition'; + +interface LogThresholdRatioChartProps { + index: IndexPattern; + threshold: Threshold; + timeRange: { from: string; to: string }; + alertRange: Timerange; + numeratorKql: string; + denominatorKql: string; + height: number; + interval?: string; + filter?: string; +} + +export function LogThresholdRatioChart({ + numeratorKql, + denominatorKql, + index, + threshold, + timeRange, + alertRange, + height, + interval = 'auto', + filter = '', +}: LogThresholdRatioChartProps) { + const { + lens: { EmbeddableComponent }, + } = useKibanaContextForPlugin().services; + const { euiTheme } = useEuiTheme(); + const lensDef = createLensDefinitionForRatioChart( + index, + euiTheme, + numeratorKql, + denominatorKql, + threshold, + alertRange, + interval, + filter + ); + return ( +
+ +
+ ); +} diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx index e13c6cec74545..504c1077093f7 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { LIGHT_THEME } from '@elastic/charts'; import { EuiPanel } from '@elastic/eui'; @@ -18,28 +18,22 @@ import moment from 'moment'; import { useTheme } from '@emotion/react'; import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { - AlertAnnotation, - getPaddedAlertTimeRange, - AlertActiveTimeRangeAnnotation, -} from '@kbn/observability-alert-details'; -import { useEuiTheme } from '@elastic/eui'; -import { UI_SETTINGS } from '@kbn/data-plugin/public'; -import { get } from 'lodash'; +import { getPaddedAlertTimeRange } from '@kbn/observability-alert-details'; +import { get, identity } from 'lodash'; import { CoPilotContextProvider } from '@kbn/observability-plugin/public'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; -import { getChartGroupNames } from '../../../../../common/utils/get_chart_group_names'; import { + Comparator, ComparatorToi18nMap, ComparatorToi18nSymbolsMap, isRatioRule, type PartialCriterion, } from '../../../../../common/alerting/logs/log_threshold'; -import { CriterionPreview } from '../expression_editor/criterion_preview_chart'; import { AlertDetailsAppSectionProps } from './types'; import { Threshold } from '../../../common/components/threshold'; -import LogsRatioChart from './components/logs_ratio_chart'; import { ExplainLogRateSpikes } from './components/explain_log_rate_spike'; +import { LogThresholdCountChart, LogThresholdRatioChart } from './components/threhsold_chart'; +import { useLogView } from '../../../../hooks/use_log_view'; import { useLicense } from '../../../../hooks/use_license'; const LogsHistoryChart = React.lazy(() => import('./components/logs_history_chart')); @@ -50,12 +44,30 @@ const AlertDetailsAppSection = ({ alert, setAlertSummaryFields, }: AlertDetailsAppSectionProps) => { - const [selectedSeries, setSelectedSeries] = useState(''); - const { uiSettings, observability } = useKibanaContextForPlugin().services; - const { euiTheme } = useEuiTheme(); + const { observability, logViews } = useKibanaContextForPlugin().services; const theme = useTheme(); const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; + const interval = `${rule.params.timeSize}${rule.params.timeUnit}`; + const thresholdFill = convertComparatorToFill(rule.params.count.comparator); + const filter = rule.params.groupBy + ? rule.params.groupBy + .map((field) => { + const value = get( + alert.fields[ALERT_CONTEXT], + ['groupByKeys', ...field.split('.')], + null + ); + return value ? `${field} : "${value}"` : null; + }) + .filter(identity) + .join(' AND ') + : ''; + + const { derivedDataView } = useLogView({ + initialLogViewReference: rule.params.logView, + logViews: logViews.client, + }); const { hasAtLeast } = useLicense(); const hasLicenseForExplainLogSpike = hasAtLeast('platinum'); @@ -80,7 +92,6 @@ const AlertDetailsAppSection = ({ {} ) || {}; - setSelectedSeries(getChartGroupNames(Object.values(alertFieldsFromGroupBy))); const alertSummaryFields = Object.entries(alertFieldsFromGroupBy).map(([label, value]) => ({ label, value, @@ -90,6 +101,13 @@ const AlertDetailsAppSection = ({ const getLogRatioChart = () => { if (isRatioRule(rule.params.criteria)) { + const numeratorKql = rule.params.criteria[0] + .map((criteria) => convertCriteriaToKQL(criteria)) + .join(' AND '); + const denominatorKql = rule.params.criteria[1] + .map((criteria) => convertCriteriaToKQL(criteria)) + .join(' AND '); + return ( @@ -106,7 +124,6 @@ const AlertDetailsAppSection = ({ - - - , - , - ]} - /> + {derivedDataView && ( + + )} @@ -160,84 +161,66 @@ const AlertDetailsAppSection = ({ const getLogCountChart = () => { if (!!rule.params.criteria && !isRatioRule(rule.params.criteria)) { - return rule.params.criteria.map((criteria, idx) => { - const chartCriterion = criteria as PartialCriterion; - return ( - - - {chartCriterion.comparator && ( - - -

- {i18n.translate('xpack.infra.logs.alertDetails.chart.chartTitle', { - defaultMessage: 'Logs for {field} {comparator} {value}', - values: { - field: chartCriterion.field, - comparator: ComparatorToi18nMap[chartCriterion.comparator], - value: chartCriterion.value, - }, - })} -

-
-
- )} -
- - - - - {chartCriterion.comparator && ( - - )} - - - convertCriteriaToKQL(criteria)) + .join(' AND '); + const criteriaAsText = rule.params.criteria + .map((criteria) => { + if (!criteria.field || !criteria.comparator || !criteria.value) { + return ''; + } + return `${criteria.field} ${ComparatorToi18nMap[criteria.comparator]} ${criteria.value}`; + }) + .filter((text) => text) + .join(' AND '); + return ( + + + + +

+ {i18n.translate('xpack.infra.logs.alertDetails.chart.chartTitle', { + defaultMessage: 'Logs for {criteria}', + values: { criteria: criteriaAsText }, + })} +

+
+
+
+ + + + + + + + {derivedDataView && ( + , - , - ]} - filterSeriesByGroupName={[selectedSeries]} + height={150} + interval={interval} /> - - -
- ); - }); + )} +
+
+
+ ); } else return null; }; @@ -269,5 +252,45 @@ const AlertDetailsAppSection = ({ ); }; + +function convertComparatorToFill(comparator: Comparator) { + switch (comparator) { + case Comparator.GT: + case Comparator.GT_OR_EQ: + return 'above'; + default: + return 'below'; + } +} + +function convertCriteriaToKQL(criteria: PartialCriterion) { + if (!criteria.value || !criteria.comparator || !criteria.field) { + return ''; + } + + switch (criteria.comparator) { + case Comparator.MATCH: + case Comparator.EQ: + return `${criteria.field} : "${criteria.value}"`; + case Comparator.NOT_MATCH: + case Comparator.NOT_EQ: + return `NOT ${criteria.field} : "${criteria.value}"`; + case Comparator.MATCH_PHRASE: + return `${criteria.field} : ${criteria.value}`; + case Comparator.NOT_MATCH_PHRASE: + return `NOT ${criteria.field} : ${criteria.value}`; + case Comparator.GT: + return `${criteria.field} > ${criteria.value}`; + case Comparator.GT_OR_EQ: + return `${criteria.field} >= ${criteria.value}`; + case Comparator.LT: + return `${criteria.field} < ${criteria.value}`; + case Comparator.LT_OR_EQ: + return `${criteria.field} <= ${criteria.value}`; + default: + return ''; + } +} + // eslint-disable-next-line import/no-default-export export default AlertDetailsAppSection; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d8d70777ce1c3..1bf79f5f8453e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18179,7 +18179,7 @@ "xpack.infra.linkTo.hostWithIp.loading": "Chargement de l'hôte avec l'adresse IP \"{hostIp}\" en cours.", "xpack.infra.logFlyout.flyoutSubTitle": "À partir de l'index {indexName}", "xpack.infra.logFlyout.flyoutTitle": "Détails de l'entrée de log {logEntryId}", - "xpack.infra.logs.alertDetails.chart.chartTitle": "Logs pour {field} {comparator} {value}", + "xpack.infra.logs.alertDetails.chart.chartTitle": "Logs pour {criteria}", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "Lors de la définition d'une valeur \"regrouper par\", nous recommandons fortement d'utiliser le comparateur \"{comparator}\" pour votre seuil. Cela peut permettre d'améliorer considérablement les performances.", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "{actualCount, plural, one {la {actualCount} dernière entrée de log} many {les {actualCount} dernières entrées de log} other {les {actualCount} dernières entrées de log}} dans {duration} pour {groupName}. Alerte lorsque {comparator} {expectedCount}.", "xpack.infra.logs.alerting.threshold.groupedRatioAlertReasonDescription": "Le rapport des logs sélectionnés est de {actualRatio} dans les dernières {duration} pour {groupName}. Alerte lorsque {comparator} {expectedRatio}.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 85fdb6b31f7d1..9ef920e63cfab 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18178,7 +18178,7 @@ "xpack.infra.linkTo.hostWithIp.loading": "IPアドレス「{hostIp}」のホストを読み込み中です。", "xpack.infra.logFlyout.flyoutSubTitle": "インデックス{indexName}から", "xpack.infra.logFlyout.flyoutTitle": "ログエントリ{logEntryId}の詳細", - "xpack.infra.logs.alertDetails.chart.chartTitle": "{field} {comparator} {value}のログ", + "xpack.infra.logs.alertDetails.chart.chartTitle": "{criteria} のログ", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "「group by」を設定するときには、しきい値で\"{comparator}\"比較演算子を使用することを強くお勧めします。これにより、パフォーマンスを大きく改善できます。", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "{groupName}の過去{duration}の{actualCount, plural, other {{actualCount}件のログエントリ}}。{comparator} {expectedCount}のときにアラートを通知します。", "xpack.infra.logs.alerting.threshold.groupedRatioAlertReasonDescription": "{groupName}の過去{duration}における選択されたログの比率は{actualRatio}です。{comparator} {expectedRatio}のときにアラートを通知します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9ec3fb5a34817..030e79dc6a4c1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18178,7 +18178,7 @@ "xpack.infra.linkTo.hostWithIp.loading": "正在加载 IP 地址为“{hostIp}”的主机。", "xpack.infra.logFlyout.flyoutSubTitle": "从索引 {indexName}", "xpack.infra.logFlyout.flyoutTitle": "日志条目 {logEntryId} 的详细信息", - "xpack.infra.logs.alertDetails.chart.chartTitle": "{field} {comparator} {value} 的日志", + "xpack.infra.logs.alertDetails.chart.chartTitle": "{criteria} 的日志", "xpack.infra.logs.alertFlyout.groupByOptimizationWarning": "设置“分组依据”时,强烈建议将“{comparator}”比较符用于阈值。这会使性能有较大提升。", "xpack.infra.logs.alerting.threshold.groupedCountAlertReasonDescription": "对于 {groupName},过去 {duration}中有 {actualCount, plural, other {{actualCount} 个日志条目}}。{comparator} {expectedCount} 时告警。", "xpack.infra.logs.alerting.threshold.groupedRatioAlertReasonDescription": "对于 {groupName},过去 {duration}选定日志的比率为 {actualRatio}。{comparator} {expectedRatio} 时告警。",