diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 21f5db55fce6fa..fc8f635394b50e 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -186,7 +186,11 @@ export const LogRateAnalysisResults: FC = ({ ); const [shouldStart, setShouldStart] = useState(false); const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId); - const [skippedColumns, setSkippedColumns] = useState(['p-value']); + const [skippedColumns, setSkippedColumns] = useState([ + 'p-value', + 'Baseline rate', + 'Deviation rate', + ]); const onGroupResultsToggle = (optionId: string) => { setToggleIdSelected(optionId); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_baseline_and_deviation_rates.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_baseline_and_deviation_rates.ts new file mode 100644 index 00000000000000..c5eb9e75ad7a51 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_baseline_and_deviation_rates.ts @@ -0,0 +1,97 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis'; + +export function getLogRateChange( + analysisType: typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE], + baselineBucketRate: number, + deviationBucketRate: number +) { + let message; + let factor; + + if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) { + if (baselineBucketRate > 0) { + factor = Math.round(((deviationBucketRate / baselineBucketRate) * 100) / 100); + message = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorIncreaseLabel', + { + defaultMessage: '{factor}x higher', + values: { + factor, + }, + } + ); + } else { + message = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel', + { + defaultMessage: + '{deviationBucketRate} {deviationBucketRate, plural, one {doc} other {docs}} rate up from 0 in baseline', + values: { deviationBucketRate }, + } + ); + } + } else { + if (deviationBucketRate > 0) { + // For dip, "doc count" refers to the amount of documents in the baseline time range so we use baselineBucketRate + factor = Math.round(((baselineBucketRate / deviationBucketRate) * 100) / 100); + message = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorDecreaseLabel', + { + defaultMessage: '{factor}x lower', + values: { + factor, + }, + } + ); + } else { + message = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocDecreaseLabel', + { + defaultMessage: 'docs rate down to 0 from {baselineBucketRate} in baseline', + values: { baselineBucketRate }, + } + ); + } + } + + return { message, factor }; +} + +export function getBaselineAndDeviationRates( + analysisType: typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE], + baselineBuckets: number, + deviationBuckets: number, + docCount: number | undefined, + bgCount: number | undefined +) { + let baselineBucketRate; + let deviationBucketRate; + if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) { + if (bgCount !== undefined) { + baselineBucketRate = Math.round(bgCount / baselineBuckets); + } + + if (docCount !== undefined) { + deviationBucketRate = Math.round(docCount / deviationBuckets); + } + } else { + // For dip, the "doc count" refers to the amount of documents in the baseline time range so we set baselineBucketRate + if (docCount !== undefined) { + baselineBucketRate = Math.round(docCount / baselineBuckets); + } + + if (bgCount !== undefined) { + deviationBucketRate = Math.round(bgCount / deviationBuckets); + } + } + + return { baselineBucketRate, deviationBucketRate }; +} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx index 3124e18a29ebe7..7f9df2468acf77 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx @@ -149,6 +149,7 @@ export const LogRateAnalysisResultsGroupsTable: FC s.logRateAnalysisStream.isRunning); const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); + const { + analysisType, + windowParameters, + documentStats: { documentCountStats }, + } = useAppSelector((s) => s.logRateAnalysis); const isGroupsTable = tableType === LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE.GROUPS; + const interval = documentCountStats?.interval ?? 0; const fieldStatsServices: FieldStatsServices = useMemo(() => { return { @@ -130,11 +180,22 @@ export const useColumns = ( }; }, [uiSettings, data, fieldFormats, charts]); + const buckets = useMemo(() => { + if (windowParameters === undefined) return; + + const { baselineMin, baselineMax, deviationMin, deviationMax } = windowParameters; + const baselineBuckets = (baselineMax - baselineMin) / interval; + const deviationBuckets = (deviationMax - deviationMin) / interval; + + return { baselineBuckets, deviationBuckets }; + }, [windowParameters, interval]); + const columnsMap: Record> = useMemo( () => ({ ['Field name']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldName', field: 'fieldName', + width: skippedColumns.length < 3 ? '17%' : '25%', name: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldNameLabel', { defaultMessage: 'Field name', }), @@ -197,6 +258,7 @@ export const useColumns = ( ['Field value']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldValue', field: 'fieldValue', + width: skippedColumns.length < 3 ? '17%' : '25%', name: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldValueLabel', { defaultMessage: 'Field value', }), @@ -220,7 +282,7 @@ export const useColumns = ( }, ['Log rate']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnLogRate', - width: NARROW_COLUMN_WIDTH, + width: '8%', field: 'pValue', name: ( <> @@ -253,7 +315,7 @@ export const useColumns = ( }, ['Impact']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnImpact', - width: NARROW_COLUMN_WIDTH, + width: '8%', field: 'pValue', name: ( <> @@ -280,9 +342,149 @@ export const useColumns = ( sortable: true, valign: 'middle', }, + ['Baseline rate']: { + 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnBaselineRateChange', + field: 'bg_count', + name: ( + <> + +   + + + ), + render: (_, { bg_count: bgCount, doc_count: docCount }) => { + if ( + interval === 0 || + windowParameters === undefined || + buckets === undefined || + isGroupsTable + ) + return NOT_AVAILABLE; + + const { baselineBucketRate } = getBaselineAndDeviationRates( + analysisType, + buckets.baselineBuckets, + buckets.deviationBuckets, + docCount, + bgCount + ); + + return <>{baselineBucketRate}; + }, + sortable: true, + valign: 'middle', + }, + ['Deviation rate']: { + 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnDeviationRateChange', + field: 'doc_count', + name: ( + <> + +   + + + ), + render: (_, { doc_count: docCount, bg_count: bgCount }) => { + if ( + interval === 0 || + windowParameters === undefined || + buckets === undefined || + isGroupsTable + ) + return NOT_AVAILABLE; + + const { deviationBucketRate } = getBaselineAndDeviationRates( + analysisType, + buckets.baselineBuckets, + buckets.deviationBuckets, + docCount, + bgCount + ); + + return <>{deviationBucketRate}; + }, + sortable: true, + valign: 'middle', + }, + ['Log rate change']: { + 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnLogRateChange', + name: ( + <> + +   + + + ), + render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { + if ( + interval === 0 || + windowParameters === undefined || + buckets === undefined || + isGroupsTable + ) + return NOT_AVAILABLE; + + const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates( + analysisType, + buckets.baselineBuckets, + buckets.deviationBuckets, + docCount, + bgCount + ); + + const logRateChange = getLogRateChange( + analysisType, + baselineBucketRate!, + deviationBucketRate! + ); + + return ( + <> + +   + {logRateChange.message} + + ); + }, + valign: 'middle', + }, ['p-value']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnPValue', - width: NARROW_COLUMN_WIDTH, field: 'pValue', name: ( <> @@ -315,7 +517,7 @@ export const useColumns = ( 'data-test-subj': isGroupsTable ? 'aiopsLogRateAnalysisResultsGroupsTableColumnDocCount' : 'aiopsLogRateAnalysisResultsTableColumnDocCount', - width: NARROW_COLUMN_WIDTH, + width: '8%', field: isGroupsTable ? 'docCount' : 'doc_count', name: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.docCountLabel', { defaultMessage: 'Doc count', @@ -333,7 +535,7 @@ export const useColumns = ( ...(viewInLogPatternAnalysisAction ? [viewInLogPatternAnalysisAction] : []), copyToClipBoardAction, ], - width: ACTIONS_COLUMN_WIDTH, + width: '4%', valign: 'middle', }, unique: { diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts index d2c3f1987667b9..e92f6aa7121a31 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts @@ -151,7 +151,16 @@ export const getArtificialLogDataViewTestData = ({ analysisGroupsTable: getAnalysisGroupsTable(), filteredAnalysisGroupsTable: getFilteredAnalysisGroupsTable(), analysisTable: getAnalysisTable(), - columnSelectorPopover: ['Log rate', 'Doc count', 'p-value', 'Impact', 'Actions'], + columnSelectorPopover: [ + 'Log rate', + 'Doc count', + 'p-value', + 'Impact', + 'Baseline rate', + 'Deviation rate', + 'Log rate change', + 'Actions', + ], fieldSelectorPopover: getFieldSelectorPopover(), globalState: { refreshInterval: { pause: true, value: 60000 }, diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts index 84e5cbed3e4001..9ad71506c82cf6 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts @@ -25,7 +25,16 @@ export const farequoteDataViewTestData: TestData = { expected: { totalDocCountFormatted: '86,374', sampleProbabilityFormatted: '0.5', - columnSelectorPopover: ['Log rate', 'Doc count', 'p-value', 'Impact', 'Actions'], + columnSelectorPopover: [ + 'Log rate', + 'Doc count', + 'p-value', + 'Impact', + 'Baseline rate', + 'Deviation rate', + 'Log rate change', + 'Actions', + ], fieldSelectorPopover: ['airline', 'custom_field.keyword'], globalState: { refreshInterval: { pause: true, value: 60000 }, diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts index 42fddac191988a..9a0295f2b55bbd 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts @@ -44,7 +44,16 @@ export const farequoteDataViewTestDataWithQuery: TestData = { impact: 'High', }, ], - columnSelectorPopover: ['Log rate', 'Doc count', 'p-value', 'Impact', 'Actions'], + columnSelectorPopover: [ + 'Log rate', + 'Doc count', + 'p-value', + 'Impact', + 'Baseline rate', + 'Deviation rate', + 'Log rate change', + 'Actions', + ], fieldSelectorPopover: ['airline', 'custom_field.keyword'], globalState: { refreshInterval: { pause: true, value: 60000 }, diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts index 9645863ed1e822..9759cc149bf9fb 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts @@ -70,7 +70,16 @@ export const kibanaLogsDataViewTestData: TestData = { logRate: 'Chart type:bar chart', impact: 'High', })), - columnSelectorPopover: ['Log rate', 'Doc count', 'p-value', 'Impact', 'Actions'], + columnSelectorPopover: [ + 'Log rate', + 'Doc count', + 'p-value', + 'Impact', + 'Baseline rate', + 'Deviation rate', + 'Log rate change', + 'Actions', + ], fieldSelectorPopover: [ 'agent.keyword', 'clientip',