mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML][AIOps] Log rate analysis: ensure ability to sort on Log rate change (#193501)
## Summary This PR - updates the `LogRateAnalysisResultsTable` to use `EuiInMemoryTable` to simplify sorting and pagination - adds sorting to `Log rate change` column - persists columns selected for viewing in the result view Related meta issue: https://github.com/elastic/kibana/issues/187684 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
446750cb9d
commit
c18184ae26
6 changed files with 137 additions and 85 deletions
|
@ -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 {
|
||||
|
|
|
@ -38,7 +38,10 @@ export function getLogRateChange(
|
|||
}
|
||||
);
|
||||
|
||||
return { message, factor: roundedFactor };
|
||||
return {
|
||||
message,
|
||||
factor: roundedFactor,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: i18n.translate(
|
||||
|
@ -66,7 +69,10 @@ export function getLogRateChange(
|
|||
}
|
||||
);
|
||||
|
||||
return { message, factor: roundedFactor };
|
||||
return {
|
||||
message,
|
||||
factor: roundedFactor,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: i18n.translate(
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
|
|
|
@ -51,7 +51,15 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
|
|||
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 : Number.POSITIVE_INFINITY,
|
||||
}));
|
||||
}, [allItems]);
|
||||
|
||||
const significantItems = useMemo(() => {
|
||||
if (!groupFilter) {
|
||||
|
@ -85,7 +93,6 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
|
|||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [sortField, setSortField] = useState<keyof SignificantItem>(
|
||||
|
@ -135,8 +142,8 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
|
|||
];
|
||||
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);
|
||||
}
|
||||
|
@ -242,12 +249,12 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
|
|||
<EuiBasicTable
|
||||
data-test-subj="aiopsLogRateAnalysisResultsTable"
|
||||
compressed
|
||||
columns={columns}
|
||||
items={pageOfItems}
|
||||
onChange={onChange}
|
||||
columns={columns}
|
||||
pagination={pagination.totalItemCount > pagination.pageSize ? pagination : undefined}
|
||||
loading={false}
|
||||
sorting={sorting as EuiTableSortingType<SignificantItem>}
|
||||
loading={false}
|
||||
onChange={onChange}
|
||||
rowProps={(significantItem) => {
|
||||
return {
|
||||
'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`,
|
||||
|
|
|
@ -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 = (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.logRateAnalysis.resultsTable.logRateChangeLabel"
|
||||
defaultMessage="Log rate change"
|
||||
/>
|
||||
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
position="top"
|
||||
color="subdued"
|
||||
type="questionInCircle"
|
||||
className="eui-alignTop"
|
||||
content={logRateChangeMessage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const ImpactColumnName = (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.logRateAnalysis.resultsTable.impactLabel"
|
||||
defaultMessage="Impact"
|
||||
/>
|
||||
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
position="top"
|
||||
color="subdued"
|
||||
type="questionInCircle"
|
||||
className="eui-alignTop"
|
||||
content={i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip', {
|
||||
defaultMessage: 'The level of impact of the field on the message rate difference.',
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const GroupImpactColumnName = (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.logRateAnalysis.resultsTable.impactLabel"
|
||||
defaultMessage="Impact"
|
||||
/>
|
||||
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
position="top"
|
||||
color="subdued"
|
||||
type="questionInCircle"
|
||||
className="eui-alignTop"
|
||||
content={i18n.translate(
|
||||
'xpack.aiops.logRateAnalysis.resultsTableGroups.impactLabelColumnTooltip',
|
||||
{
|
||||
defaultMessage: 'The level of impact of the group on the message rate difference',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
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<ColumnNames, EuiBasicTableColumn<SignificantItem>> = useMemo(
|
||||
() => ({
|
||||
['Field name']: {
|
||||
|
@ -322,24 +398,8 @@ export const useColumns = (
|
|||
'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnImpact',
|
||||
width: '8%',
|
||||
field: 'pValue',
|
||||
name: (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.logRateAnalysis.resultsTable.impactLabel"
|
||||
defaultMessage="Impact"
|
||||
/>
|
||||
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
position="top"
|
||||
color="subdued"
|
||||
type="questionInCircle"
|
||||
className="eui-alignTop"
|
||||
content={isGroupsTable ? groupImpactMessage : impactMessage}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
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 ? <EuiBadge color={label.color}>{label.impact}</EuiBadge> : null;
|
||||
|
@ -435,46 +495,12 @@ export const useColumns = (
|
|||
},
|
||||
['Log rate change']: {
|
||||
'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnLogRateChange',
|
||||
name: (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.logRateAnalysis.resultsTable.logRateChangeLabel"
|
||||
defaultMessage="Log rate change"
|
||||
/>
|
||||
|
||||
<EuiIconTip
|
||||
size="s"
|
||||
position="top"
|
||||
color="subdued"
|
||||
type="questionInCircle"
|
||||
className="eui-alignTop"
|
||||
content={logRateChangeMessage}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
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
|
||||
);
|
||||
field: 'logRateChangeSort',
|
||||
name: LogRateColumnName,
|
||||
sortable: isGroupsTable ? false : true,
|
||||
render: (_, { doc_count: docCount, bg_count: bgCount }: SignificantItem) => {
|
||||
if (logRateChangeNotAvailable) return NOT_AVAILABLE;
|
||||
const logRateChange = getLogRateChangeValues(docCount, bgCount);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -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<AiOps, null>;
|
||||
|
@ -37,6 +39,8 @@ export type AiOpsStorageMapped<T extends AiOpsKey> = 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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue