Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML][AIOps] Log rate analysis: ensure ability to sort on Log rate change #193501

Merged
17 changes: 14 additions & 3 deletions x-pack/packages/ml/aiops_log_rate_analysis/get_log_rate_change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -48,6 +53,7 @@ export function getLogRateChange(
values: { deviationBucketRate },
}
),
factor: deviationBucketRate,
};
}
} else {
Expand All @@ -66,7 +72,11 @@ export function getLogRateChange(
}
);

return { message, factor: roundedFactor };
return {
message,
factor: roundedFactor,
sortableValue: baselineBucketRate - deviationBucketRate,
};
} else {
return {
message: i18n.translate(
Expand All @@ -76,6 +86,7 @@ export function getLogRateChange(
values: { baselineBucketRate },
}
),
factor: baselineBucketRate,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -187,11 +193,10 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
);
const [shouldStart, setShouldStart] = useState(false);
const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId);
const [skippedColumns, setSkippedColumns] = useState<ColumnNames[]>([
'p-value',
'Baseline rate',
'Deviation rate',
]);
const [skippedColumns, setSkippedColumns] = useStorage<
AiOpsKey,
AiOpsStorageMapped<typeof AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS>
>(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<string[] | null>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -86,13 +86,15 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =

const dispatch = useAppDispatch();

const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const [sortField, setSortField] = useState<keyof SignificantItem>(
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<SignificantItem>(
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(
Expand All @@ -104,82 +106,26 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
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
) {
Expand All @@ -189,7 +135,7 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
dispatch,
selectedGroup,
selectedSignificantItem,
pageOfItems,
significantItems,
pinnedGroup,
pinnedSignificantItem,
]);
Expand Down Expand Up @@ -239,15 +185,15 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
// running the analysis and will hide this table.

return (
<EuiBasicTable
<EuiInMemoryTable<SignificantItem>
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<SignificantItem>}
onChange={onTableChange}
rowProps={(significantItem) => {
return {
'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`,
Expand Down
Loading