From b701e275d52733705b0737c7c6d2c930fa8827f6 Mon Sep 17 00:00:00 2001 From: Melissa Date: Thu, 19 Sep 2024 18:07:42 -0600 Subject: [PATCH 1/6] wip: convert logRateAnalysis result table to in memory table for simplification and ease of sorting --- .../get_log_rate_change.ts | 2 + .../log_rate_analysis_results_table.tsx | 100 ++++-------------- .../use_columns.tsx | 26 +++++ 3 files changed, 51 insertions(+), 77 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts index 39e0c4e6f7bdc..723dd0eb8c515 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts @@ -48,6 +48,7 @@ export function getLogRateChange( values: { deviationBucketRate }, } ), + factor: deviationBucketRate, }; } } else { @@ -76,6 +77,7 @@ export function getLogRateChange( values: { baselineBucketRate }, } ), + factor: baselineBucketRate, }; } } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index f6f3ea0f4d9db..8b44af3fdb15f 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -6,14 +6,14 @@ */ import type { FC } from 'react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { orderBy, isEqual } from 'lodash'; +import React, { useEffect, useMemo } from 'react'; +import { isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { EuiTableSortingType } from '@elastic/eui'; -import { useEuiBackgroundColor, EuiBasicTable } from '@elastic/eui'; +import { useEuiBackgroundColor, EuiInMemoryTable } from '@elastic/eui'; import type { SignificantItem } from '@kbn/ml-agg-utils'; +import { useTableState } from '@kbn/ml-in-memory-table'; import { setPinnedSignificantItem, setSelectedSignificantItem, @@ -86,13 +86,15 @@ export const LogRateAnalysisResultsTable: FC = const dispatch = useAppDispatch(); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - const [sortField, setSortField] = useState( - zeroDocsFallback ? DEFAULT_SORT_FIELD_ZERO_DOCS_FALLBACK : DEFAULT_SORT_FIELD - ); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>( - zeroDocsFallback ? DEFAULT_SORT_DIRECTION_ZERO_DOCS_FALLBACK : DEFAULT_SORT_DIRECTION + const { onTableChange, pagination, sorting } = useTableState( + significantItems ?? [], + zeroDocsFallback ? DEFAULT_SORT_FIELD_ZERO_DOCS_FALLBACK : DEFAULT_SORT_FIELD, + zeroDocsFallback ? DEFAULT_SORT_DIRECTION_ZERO_DOCS_FALLBACK : DEFAULT_SORT_DIRECTION, + { + pageIndex: 0, + pageSize: 10, + pageSizeOptions: PAGINATION_SIZE_OPTIONS, + } ); const columns = useColumns( @@ -104,82 +106,26 @@ export const LogRateAnalysisResultsTable: FC = groupFilter !== undefined ); - const onChange = useCallback((tableSettings: any) => { - if (tableSettings.page) { - const { index, size } = tableSettings.page; - setPageIndex(index); - setPageSize(size); - } - - if (tableSettings.sort) { - const { field, direction } = tableSettings.sort; - setSortField(field); - setSortDirection(direction); - } - }, []); - - const { pagination, pageOfItems, sorting } = useMemo(() => { - const pageStart = pageIndex * pageSize; - const itemCount = significantItems?.length ?? 0; - - let items: SignificantItem[] = significantItems ?? []; - - const sortIteratees = [ - (item: SignificantItem) => { - if (item && typeof item[sortField] === 'string') { - // @ts-ignore Object is possibly null or undefined - return item[sortField].toLowerCase(); - } - return item[sortField]; - }, - ]; - const sortDirections = [sortDirection]; - - // Only if the table is sorted by p-value, add a secondary sort by doc count. - if (sortField === 'pValue') { - sortIteratees.push((item: SignificantItem) => item.doc_count); - sortDirections.push(sortDirection); - } - - items = orderBy(significantItems, sortIteratees, sortDirections); - - return { - pageOfItems: items.slice(pageStart, pageStart + pageSize), - pagination: { - pageIndex, - pageSize, - totalItemCount: itemCount, - pageSizeOptions: PAGINATION_SIZE_OPTIONS, - }, - sorting: { - sort: { - field: sortField, - direction: sortDirection, - }, - }, - }; - }, [pageIndex, pageSize, sortField, sortDirection, significantItems]); - useEffect(() => { // If no row is hovered or pinned or the user switched to a new page, // fall back to set the first row into a hovered state to make the // main document count chart show a comparison view by default. if ( (selectedSignificantItem === null || - !pageOfItems.some((item) => isEqual(item, selectedSignificantItem))) && + !significantItems.some((item) => isEqual(item, selectedSignificantItem))) && pinnedSignificantItem === null && - pageOfItems.length > 0 && + significantItems.length > 0 && selectedGroup === null && pinnedGroup === null ) { - dispatch(setSelectedSignificantItem(pageOfItems[0])); + dispatch(setSelectedSignificantItem(significantItems[0])); } // If a user switched pages and a pinned row is no longer visible // on the current page, set the status of pinned rows back to `null`. if ( pinnedSignificantItem !== null && - !pageOfItems.some((item) => isEqual(item, pinnedSignificantItem)) && + !significantItems.some((item) => isEqual(item, pinnedSignificantItem)) && selectedGroup === null && pinnedGroup === null ) { @@ -189,7 +135,7 @@ export const LogRateAnalysisResultsTable: FC = dispatch, selectedGroup, selectedSignificantItem, - pageOfItems, + significantItems, pinnedGroup, pinnedSignificantItem, ]); @@ -239,15 +185,15 @@ export const LogRateAnalysisResultsTable: FC = // running the analysis and will hide this table. return ( - data-test-subj="aiopsLogRateAnalysisResultsTable" compressed + items={significantItems} columns={columns} - items={pageOfItems} - onChange={onChange} - pagination={pagination.totalItemCount > pagination.pageSize ? pagination : undefined} + pagination={pagination} + sorting={sorting} loading={false} - sorting={sorting as EuiTableSortingType} + onTableChange={onTableChange} rowProps={(significantItem) => { return { 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index 637aa0dc69b39..fac2f7e04ad40 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -452,6 +452,32 @@ export const useColumns = ( /> ), + sortable: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { + // TODO: Move this duplicated calculation into a separate function + if ( + interval === 0 || + currentAnalysisType === undefined || + currentAnalysisWindowParameters === undefined || + buckets === undefined || + isGroupsTable + ) + return NOT_AVAILABLE; + + const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates( + currentAnalysisType, + buckets.baselineBuckets, + buckets.deviationBuckets, + docCount, + bgCount + ); + + const logRateChange = getLogRateChange( + currentAnalysisType, + baselineBucketRate, + deviationBucketRate + ); + return logRateChange.factor; + }, render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { if ( interval === 0 || From 2866b7818db6e7732769a04af6db4a8335eab14c Mon Sep 17 00:00:00 2001 From: Melissa Date: Fri, 20 Sep 2024 16:28:12 -0600 Subject: [PATCH 2/6] ensure log rate change and impact can be sorted correctly --- .../get_log_rate_change.ts | 15 +- .../log_rate_analysis_results_table.tsx | 2 +- .../use_columns.tsx | 200 +++++++++--------- 3 files changed, 115 insertions(+), 102 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts index 723dd0eb8c515..8e48907516b9f 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts @@ -22,7 +22,7 @@ export function getLogRateChange( analysisType: LogRateAnalysisType, baselineBucketRate: number, deviationBucketRate: number -): { message: string; factor?: number } { +): { message: string; factor: number; sortableValue?: number } { if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) { if (baselineBucketRate > 0) { const factor = deviationBucketRate / baselineBucketRate; @@ -38,7 +38,12 @@ export function getLogRateChange( } ); - return { message, factor: roundedFactor }; + return { + message, + factor: roundedFactor, + // ensure factor change is always higher in sorting value than just docs up from 0 + sortableValue: deviationBucketRate + baselineBucketRate, + }; } else { return { message: i18n.translate( @@ -67,7 +72,11 @@ export function getLogRateChange( } ); - return { message, factor: roundedFactor }; + return { + message, + factor: roundedFactor, + sortableValue: baselineBucketRate - deviationBucketRate, + }; } else { return { message: i18n.translate( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 8b44af3fdb15f..4205a289b4fbf 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -193,7 +193,7 @@ export const LogRateAnalysisResultsTable: FC = pagination={pagination} sorting={sorting} loading={false} - onTableChange={onTableChange} + onChange={onTableChange} rowProps={(significantItem) => { return { 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index fac2f7e04ad40..d8fce9e0a2cb0 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { type EuiBasicTableColumn, EuiBadge, @@ -112,18 +112,6 @@ const groupLogRateHelpMessage = i18n.translate( 'A visual representation of the impact of the group on the message rate difference.', } ); -const groupImpactMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTableGroups.impactLabelColumnTooltip', - { - defaultMessage: 'The level of impact of the group on the message rate difference', - } -); -const impactMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip', - { - defaultMessage: 'The level of impact of the field on the message rate difference.', - } -); const logRateChangeMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateChangeLabelColumnTooltip', { @@ -144,6 +132,69 @@ const deviationRateMessage = i18n.translate( } ); +// EuiInMemoryTable stores column `name` in its own memory as an object and computed columns may be updating dynamically which means the reference is no longer ===. +// Need to declare name react node outside to keep it static and maintain reference equality. +const LogRateColumnName = ( + <> + +   + + +); + +const ImpactColumnName = ( + <> + +   + + +); + +const GroupImpactColumnName = ( + <> + +   + + +); + export const useColumns = ( tableType: LogRateAnalysisResultsTableType, skippedColumns: string[], @@ -195,6 +246,31 @@ export const useColumns = ( return { baselineBuckets, deviationBuckets }; }, [currentAnalysisWindowParameters, interval]); + const logRateChangeNotAvailable = useMemo( + () => + interval === 0 || + currentAnalysisType === undefined || + currentAnalysisWindowParameters === undefined || + buckets === undefined || + isGroupsTable, + [interval, currentAnalysisType, currentAnalysisWindowParameters, buckets, isGroupsTable] + ); + + const getLogRateChangeValues = useCallback( + (docCount: number, bgCount: number) => { + const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates( + currentAnalysisType!, + buckets!.baselineBuckets, + buckets!.deviationBuckets, + docCount, + bgCount + ); + + return getLogRateChange(currentAnalysisType!, baselineBucketRate, deviationBucketRate); + }, + [currentAnalysisType, buckets] + ); + const columnsMap: Record> = useMemo( () => ({ ['Field name']: { @@ -321,30 +397,13 @@ export const useColumns = ( ['Impact']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnImpact', width: '8%', - field: 'pValue', - name: ( - <> - -   - - - ), - render: (_, { pValue }) => { + name: isGroupsTable ? GroupImpactColumnName : ImpactColumnName, // content={isGroupsTable ? groupImpactMessage : impactMessage} + render: ({ pValue }: SignificantItem) => { if (typeof pValue !== 'number') return NOT_AVAILABLE; const label = getFailedTransactionsCorrelationImpactLabel(pValue); return label ? {label.impact} : null; }, - sortable: true, + sortable: ({ pValue }) => pValue, valign: 'middle', }, ['Baseline rate']: { @@ -435,72 +494,17 @@ export const useColumns = ( }, ['Log rate change']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnLogRateChange', - name: ( - <> - -   - - - ), - sortable: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { - // TODO: Move this duplicated calculation into a separate function - if ( - interval === 0 || - currentAnalysisType === undefined || - currentAnalysisWindowParameters === undefined || - buckets === undefined || - isGroupsTable - ) - return NOT_AVAILABLE; - - const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates( - currentAnalysisType, - buckets.baselineBuckets, - buckets.deviationBuckets, - docCount, - bgCount - ); - - const logRateChange = getLogRateChange( - currentAnalysisType, - baselineBucketRate, - deviationBucketRate - ); - return logRateChange.factor; - }, + name: LogRateColumnName, + sortable: isGroupsTable + ? undefined + : ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { + if (logRateChangeNotAvailable) return NOT_AVAILABLE; + const logRateChange = getLogRateChangeValues(docCount, bgCount); + return logRateChange.sortableValue ?? logRateChange.factor; + }, render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { - if ( - interval === 0 || - currentAnalysisType === undefined || - currentAnalysisWindowParameters === undefined || - buckets === undefined || - isGroupsTable - ) - return NOT_AVAILABLE; - - const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates( - currentAnalysisType, - buckets.baselineBuckets, - buckets.deviationBuckets, - docCount, - bgCount - ); - - const logRateChange = getLogRateChange( - currentAnalysisType, - baselineBucketRate, - deviationBucketRate - ); + if (logRateChangeNotAvailable) return NOT_AVAILABLE; + const logRateChange = getLogRateChangeValues(docCount, bgCount); return ( <> From 25e6ba77a17c6f7179faf6ee2b2b20cc57525891 Mon Sep 17 00:00:00 2001 From: Melissa Date: Fri, 20 Sep 2024 16:57:26 -0600 Subject: [PATCH 3/6] persist columns selected for display --- .../log_rate_analysis_results.tsx | 15 ++++++++++----- x-pack/plugins/aiops/public/types/storage.ts | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) 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 8c2cf12ca376c..26475f8f525e6 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 @@ -37,6 +37,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; +import { useStorage } from '@kbn/ml-local-storage'; import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants'; import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema'; import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v3'; @@ -53,6 +54,11 @@ import { commonColumns, significantItemColumns, } from '../log_rate_analysis_results_table/use_columns'; +import { + AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, + type AiOpsKey, + type AiOpsStorageMapped, +} from '../../types/storage'; import { getGroupTableItems, @@ -187,11 +193,10 @@ export const LogRateAnalysisResults: FC = ({ ); const [shouldStart, setShouldStart] = useState(false); const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId); - const [skippedColumns, setSkippedColumns] = useState([ - 'p-value', - 'Baseline rate', - 'Deviation rate', - ]); + const [skippedColumns, setSkippedColumns] = useStorage< + AiOpsKey, + AiOpsStorageMapped + >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']); // null is used as the uninitialized state to identify the first load. const [skippedFields, setSkippedFields] = useState(null); diff --git a/x-pack/plugins/aiops/public/types/storage.ts b/x-pack/plugins/aiops/public/types/storage.ts index ea6fde6b06552..a4a29dda2f2c3 100644 --- a/x-pack/plugins/aiops/public/types/storage.ts +++ b/x-pack/plugins/aiops/public/types/storage.ts @@ -19,12 +19,14 @@ export const AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE = 'aiops.randomSamplingProbabilityPreference'; export const AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE = 'aiops.patternAnalysisMinimumTimeRangePreference'; +export const AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS = 'aiops.logRateAnalysisResultColumns'; export type AiOps = Partial<{ [AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; [AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE]: RandomSamplerOption; [AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE]: number; [AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE]: MinimumTimeRangeOption; + [AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS]: string[]; }> | null; export type AiOpsKey = keyof Exclude; @@ -37,6 +39,8 @@ export type AiOpsStorageMapped = T extends typeof AIOPS_FROZ ? RandomSamplerProbability : T extends typeof AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE ? MinimumTimeRangeOption + : T extends typeof AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS + ? string[] : null; export const AIOPS_STORAGE_KEYS = [ @@ -44,4 +48,5 @@ export const AIOPS_STORAGE_KEYS = [ AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE, + AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ] as const; From c034dc521b8bd86f3d3fa65dbe8c2363ac7032a9 Mon Sep 17 00:00:00 2001 From: Melissa Date: Mon, 30 Sep 2024 16:18:34 -0600 Subject: [PATCH 4/6] ensure sorting works correctly --- .../aiops_log_rate_analysis/get_log_rate_change.ts | 12 ++++++------ .../log_rate_analysis_results_table/use_columns.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts index 8e48907516b9f..8b477ddc6fdb3 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts @@ -9,6 +9,8 @@ import { i18n } from '@kbn/i18n'; import { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type'; import type { LogRateAnalysisType } from './log_rate_analysis_type'; +const REDUCTION_FACTOR = 0.000001; + /** * Calculates the change in log rate between two time periods and generates a descriptive message. * It return the factor as a number as well as a human readable message. @@ -22,7 +24,7 @@ export function getLogRateChange( analysisType: LogRateAnalysisType, baselineBucketRate: number, deviationBucketRate: number -): { message: string; factor: number; sortableValue?: number } { +): { message: string; factor: number } { if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) { if (baselineBucketRate > 0) { const factor = deviationBucketRate / baselineBucketRate; @@ -41,8 +43,6 @@ export function getLogRateChange( return { message, factor: roundedFactor, - // ensure factor change is always higher in sorting value than just docs up from 0 - sortableValue: deviationBucketRate + baselineBucketRate, }; } else { return { @@ -53,7 +53,8 @@ export function getLogRateChange( values: { deviationBucketRate }, } ), - factor: deviationBucketRate, + // ensure factor change is always higher in sorting value than just docs up from 0 + factor: deviationBucketRate * REDUCTION_FACTOR, }; } } else { @@ -75,7 +76,6 @@ export function getLogRateChange( return { message, factor: roundedFactor, - sortableValue: baselineBucketRate - deviationBucketRate, }; } else { return { @@ -86,7 +86,7 @@ export function getLogRateChange( values: { baselineBucketRate }, } ), - factor: baselineBucketRate, + factor: baselineBucketRate * REDUCTION_FACTOR, }; } } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index d8fce9e0a2cb0..f8f61cf10a112 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -500,7 +500,7 @@ export const useColumns = ( : ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { if (logRateChangeNotAvailable) return NOT_AVAILABLE; const logRateChange = getLogRateChangeValues(docCount, bgCount); - return logRateChange.sortableValue ?? logRateChange.factor; + return logRateChange.factor; }, render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { if (logRateChangeNotAvailable) return NOT_AVAILABLE; From 559451ff8d312305a492edd85e1735fb203f466a Mon Sep 17 00:00:00 2001 From: Melissa Date: Tue, 1 Oct 2024 13:12:20 -0600 Subject: [PATCH 5/6] fix regression and keep sorting consistent with log rate analysis for the contextual insight for observability --- x-pack/packages/ml/agg_utils/src/types.ts | 3 + .../log_rate_analysis_results_table.tsx | 111 ++++++++++++++---- .../use_columns.tsx | 16 +-- 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/x-pack/packages/ml/agg_utils/src/types.ts b/x-pack/packages/ml/agg_utils/src/types.ts index 843172e323799..4feafd9554126 100644 --- a/x-pack/packages/ml/agg_utils/src/types.ts +++ b/x-pack/packages/ml/agg_utils/src/types.ts @@ -166,6 +166,9 @@ export interface SignificantItem extends FieldValuePair { /** Indicates if the significant item is unique within a group. */ unique?: boolean; + + /** Calculates a numerical value based on bg_count and doc_count for which to sort log rate change */ + logRateChangeSort?: number; } interface SignificantItemHistogramItemBase { diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 4205a289b4fbf..8b36a6d2af0ee 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -6,14 +6,14 @@ */ import type { FC } from 'react'; -import React, { useEffect, useMemo } from 'react'; -import { isEqual } from 'lodash'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { orderBy, isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { useEuiBackgroundColor, EuiInMemoryTable } from '@elastic/eui'; +import type { EuiTableSortingType } from '@elastic/eui'; +import { useEuiBackgroundColor, EuiBasicTable } from '@elastic/eui'; import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { useTableState } from '@kbn/ml-in-memory-table'; import { setPinnedSignificantItem, setSelectedSignificantItem, @@ -51,7 +51,14 @@ export const LogRateAnalysisResultsTable: FC = const euiTheme = useEuiTheme(); const primaryBackgroundColor = useEuiBackgroundColor('primary'); - const allSignificantItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems); + const allItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems); + + const allSignificantItems = useMemo(() => { + return allItems.map((item) => ({ + ...item, + logRateChangeSort: item.bg_count > 0 ? item.doc_count / item.bg_count : item.doc_count, + })); + }, [allItems]); const significantItems = useMemo(() => { if (!groupFilter) { @@ -85,16 +92,13 @@ export const LogRateAnalysisResultsTable: FC = ); const dispatch = useAppDispatch(); - - const { onTableChange, pagination, sorting } = useTableState( - significantItems ?? [], - zeroDocsFallback ? DEFAULT_SORT_FIELD_ZERO_DOCS_FALLBACK : DEFAULT_SORT_FIELD, - zeroDocsFallback ? DEFAULT_SORT_DIRECTION_ZERO_DOCS_FALLBACK : DEFAULT_SORT_DIRECTION, - { - pageIndex: 0, - pageSize: 10, - pageSizeOptions: PAGINATION_SIZE_OPTIONS, - } + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState( + zeroDocsFallback ? DEFAULT_SORT_FIELD_ZERO_DOCS_FALLBACK : DEFAULT_SORT_FIELD + ); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>( + zeroDocsFallback ? DEFAULT_SORT_DIRECTION_ZERO_DOCS_FALLBACK : DEFAULT_SORT_DIRECTION ); const columns = useColumns( @@ -106,26 +110,83 @@ export const LogRateAnalysisResultsTable: FC = groupFilter !== undefined ); + const onChange = useCallback((tableSettings: any) => { + if (tableSettings.page) { + const { index, size } = tableSettings.page; + setPageIndex(index); + setPageSize(size); + } + + if (tableSettings.sort) { + const { field, direction } = tableSettings.sort; + setSortField(field); + setSortDirection(direction); + } + }, []); + + const { pagination, pageOfItems, sorting } = useMemo(() => { + const pageStart = pageIndex * pageSize; + const itemCount = significantItems?.length ?? 0; + + let items: SignificantItem[] = significantItems ?? []; + // console.log('--- SORT FIELD ---', sortField.props.children[0]); + + const sortIteratees = [ + (item: SignificantItem) => { + if (item && typeof item[sortField] === 'string') { + // @ts-ignore Object is possibly null or undefined + return item[sortField].toLowerCase(); + } + return item[sortField]; + }, + ]; + const sortDirections = [sortDirection]; + + // Only if the table is sorted by p-value, add a secondary sort by doc count. + if (sortField === 'pValue') { + sortIteratees.push((item: SignificantItem) => item.doc_count); + sortDirections.push(sortDirection); + } + + items = orderBy(significantItems, sortIteratees, sortDirections); + + return { + pageOfItems: items.slice(pageStart, pageStart + pageSize), + pagination: { + pageIndex, + pageSize, + totalItemCount: itemCount, + pageSizeOptions: PAGINATION_SIZE_OPTIONS, + }, + sorting: { + sort: { + field: sortField, + direction: sortDirection, + }, + }, + }; + }, [pageIndex, pageSize, sortField, sortDirection, significantItems]); + useEffect(() => { // If no row is hovered or pinned or the user switched to a new page, // fall back to set the first row into a hovered state to make the // main document count chart show a comparison view by default. if ( (selectedSignificantItem === null || - !significantItems.some((item) => isEqual(item, selectedSignificantItem))) && + !pageOfItems.some((item) => isEqual(item, selectedSignificantItem))) && pinnedSignificantItem === null && - significantItems.length > 0 && + pageOfItems.length > 0 && selectedGroup === null && pinnedGroup === null ) { - dispatch(setSelectedSignificantItem(significantItems[0])); + dispatch(setSelectedSignificantItem(pageOfItems[0])); } // If a user switched pages and a pinned row is no longer visible // on the current page, set the status of pinned rows back to `null`. if ( pinnedSignificantItem !== null && - !significantItems.some((item) => isEqual(item, pinnedSignificantItem)) && + !pageOfItems.some((item) => isEqual(item, pinnedSignificantItem)) && selectedGroup === null && pinnedGroup === null ) { @@ -135,7 +196,7 @@ export const LogRateAnalysisResultsTable: FC = dispatch, selectedGroup, selectedSignificantItem, - significantItems, + pageOfItems, pinnedGroup, pinnedSignificantItem, ]); @@ -185,15 +246,15 @@ export const LogRateAnalysisResultsTable: FC = // running the analysis and will hide this table. return ( - + pagination.pageSize ? pagination : undefined} + sorting={sorting as EuiTableSortingType} loading={false} - onChange={onTableChange} + onChange={onChange} rowProps={(significantItem) => { return { 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index f8f61cf10a112..f3b8195767101 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -397,13 +397,14 @@ export const useColumns = ( ['Impact']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnImpact', width: '8%', + field: 'pValue', name: isGroupsTable ? GroupImpactColumnName : ImpactColumnName, // content={isGroupsTable ? groupImpactMessage : impactMessage} - render: ({ pValue }: SignificantItem) => { + render: (_, { pValue }: SignificantItem) => { if (typeof pValue !== 'number') return NOT_AVAILABLE; const label = getFailedTransactionsCorrelationImpactLabel(pValue); return label ? {label.impact} : null; }, - sortable: ({ pValue }) => pValue, + sortable: true, valign: 'middle', }, ['Baseline rate']: { @@ -494,15 +495,10 @@ export const useColumns = ( }, ['Log rate change']: { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnLogRateChange', + field: 'logRateChangeSort', name: LogRateColumnName, - sortable: isGroupsTable - ? undefined - : ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { - if (logRateChangeNotAvailable) return NOT_AVAILABLE; - const logRateChange = getLogRateChangeValues(docCount, bgCount); - return logRateChange.factor; - }, - render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => { + sortable: isGroupsTable ? false : true, + render: (_, { doc_count: docCount, bg_count: bgCount }: SignificantItem) => { if (logRateChangeNotAvailable) return NOT_AVAILABLE; const logRateChange = getLogRateChangeValues(docCount, bgCount); From 7f0c33b865302483af24b4b327c42bfa1d861cae Mon Sep 17 00:00:00 2001 From: Melissa Date: Wed, 2 Oct 2024 11:07:57 -0600 Subject: [PATCH 6/6] update sorting logic --- .../ml/aiops_log_rate_analysis/get_log_rate_change.ts | 7 +------ .../log_rate_analysis_results_table.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts index 8b477ddc6fdb3..426f39749520a 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts @@ -9,8 +9,6 @@ import { i18n } from '@kbn/i18n'; import { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type'; import type { LogRateAnalysisType } from './log_rate_analysis_type'; -const REDUCTION_FACTOR = 0.000001; - /** * Calculates the change in log rate between two time periods and generates a descriptive message. * It return the factor as a number as well as a human readable message. @@ -24,7 +22,7 @@ export function getLogRateChange( analysisType: LogRateAnalysisType, baselineBucketRate: number, deviationBucketRate: number -): { message: string; factor: number } { +): { message: string; factor?: number } { if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) { if (baselineBucketRate > 0) { const factor = deviationBucketRate / baselineBucketRate; @@ -53,8 +51,6 @@ export function getLogRateChange( values: { deviationBucketRate }, } ), - // ensure factor change is always higher in sorting value than just docs up from 0 - factor: deviationBucketRate * REDUCTION_FACTOR, }; } } else { @@ -86,7 +82,6 @@ export function getLogRateChange( values: { baselineBucketRate }, } ), - factor: baselineBucketRate * REDUCTION_FACTOR, }; } } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 8b36a6d2af0ee..83be306e93f50 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -56,7 +56,8 @@ export const LogRateAnalysisResultsTable: FC = const allSignificantItems = useMemo(() => { return allItems.map((item) => ({ ...item, - logRateChangeSort: item.bg_count > 0 ? item.doc_count / item.bg_count : item.doc_count, + logRateChangeSort: + item.bg_count > 0 ? item.doc_count / item.bg_count : Number.POSITIVE_INFINITY, })); }, [allItems]); @@ -129,7 +130,6 @@ export const LogRateAnalysisResultsTable: FC = const itemCount = significantItems?.length ?? 0; let items: SignificantItem[] = significantItems ?? []; - // console.log('--- SORT FIELD ---', sortField.props.children[0]); const sortIteratees = [ (item: SignificantItem) => { @@ -142,8 +142,8 @@ export const LogRateAnalysisResultsTable: FC = ]; const sortDirections = [sortDirection]; - // Only if the table is sorted by p-value, add a secondary sort by doc count. - if (sortField === 'pValue') { + // If the table is sorted by p-value or log rate change, add a secondary sort by doc count. + if (sortField === 'pValue' || sortField === 'logRateChangeSort') { sortIteratees.push((item: SignificantItem) => item.doc_count); sortDirections.push(sortDirection); }