Skip to content

Commit

Permalink
[ML][AIOps] Log rate analysis: ensure ability to sort on Log rate cha…
Browse files Browse the repository at this point in the history
…nge (elastic#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: elastic#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>
  • Loading branch information
alvarezmelissa87 and elasticmachine authored Oct 3, 2024
1 parent 446750c commit c18184a
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 85 deletions.
3 changes: 3 additions & 0 deletions x-pack/packages/ml/agg_utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export function getLogRateChange(
}
);

return { message, factor: roundedFactor };
return {
message,
factor: roundedFactor,
};
} else {
return {
message: i18n.translate(
Expand Down Expand Up @@ -66,7 +69,10 @@ export function getLogRateChange(
}
);

return { message, factor: roundedFactor };
return {
message,
factor: roundedFactor,
};
} else {
return {
message: i18n.translate(
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 @@ -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) {
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useMemo } from 'react';
import React, { useMemo, useCallback } from 'react';
import {
type EuiBasicTableColumn,
EuiBadge,
Expand Down Expand Up @@ -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',
{
Expand All @@ -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"
/>
&nbsp;
<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"
/>
&nbsp;
<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"
/>
&nbsp;
<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[],
Expand Down Expand Up @@ -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']: {
Expand Down Expand Up @@ -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"
/>
&nbsp;
<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;
Expand Down Expand Up @@ -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"
/>
&nbsp;
<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 (
<>
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/aiops/public/types/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -37,11 +39,14 @@ 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 = [
AIOPS_FROZEN_TIER_PREFERENCE,
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;

0 comments on commit c18184a

Please sign in to comment.