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
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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess we can remove the comment?

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;