mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Explain Log Rate Spikes: merge duplicate spikeAnalysisTable components (#141116)
* merge duplicate spikeAnalysisTable components * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f4dd515641
commit
130a8cde54
3 changed files with 18 additions and 358 deletions
|
@ -237,6 +237,7 @@ export const SpikeAnalysisTable: FC<SpikeAnalysisTableProps> = ({
|
|||
</EuiToolTip>
|
||||
),
|
||||
render: (_, { pValue }) => {
|
||||
if (!pValue) return NOT_AVAILABLE;
|
||||
const label = getFailedTransactionsCorrelationImpactLabel(pValue);
|
||||
return label ? <EuiBadge color={label.color}>{label.impact}</EuiBadge> : null;
|
||||
},
|
||||
|
|
|
@ -1,355 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiIcon,
|
||||
EuiTableSortingType,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { escapeKuery } from '@kbn/es-query';
|
||||
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
||||
|
||||
import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils';
|
||||
import { useEuiTheme } from '../../hooks/use_eui_theme';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
|
||||
import { MiniHistogram } from '../mini_histogram';
|
||||
|
||||
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
|
||||
|
||||
const NARROW_COLUMN_WIDTH = '120px';
|
||||
const ACTIONS_COLUMN_WIDTH = '60px';
|
||||
const NOT_AVAILABLE = '--';
|
||||
|
||||
const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50];
|
||||
const DEFAULT_SORT_FIELD = 'pValue';
|
||||
const DEFAULT_SORT_DIRECTION = 'asc';
|
||||
const viewInDiscoverMessage = i18n.translate(
|
||||
'xpack.aiops.spikeAnalysisTable.linksMenu.viewInDiscover',
|
||||
{
|
||||
defaultMessage: 'View in Discover',
|
||||
}
|
||||
);
|
||||
|
||||
interface SpikeAnalysisTableExpandedRowProps {
|
||||
changePoints: ChangePoint[];
|
||||
dataViewId?: string;
|
||||
loading: boolean;
|
||||
onPinnedChangePoint?: (changePoint: ChangePoint | null) => void;
|
||||
onSelectedChangePoint?: (changePoint: ChangePoint | null) => void;
|
||||
selectedChangePoint?: ChangePoint;
|
||||
}
|
||||
|
||||
export const SpikeAnalysisTableExpandedRow: FC<SpikeAnalysisTableExpandedRowProps> = ({
|
||||
changePoints,
|
||||
dataViewId,
|
||||
loading,
|
||||
onPinnedChangePoint,
|
||||
onSelectedChangePoint,
|
||||
selectedChangePoint,
|
||||
}) => {
|
||||
const euiTheme = useEuiTheme();
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [sortField, setSortField] = useState<keyof ChangePoint>(DEFAULT_SORT_FIELD);
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION);
|
||||
|
||||
const { application, share, data } = useAiopsAppContext();
|
||||
|
||||
const discoverLocator = useMemo(
|
||||
() => share.url.locators.get('DISCOVER_APP_LOCATOR'),
|
||||
[share.url.locators]
|
||||
);
|
||||
|
||||
const discoverUrlError = useMemo(() => {
|
||||
if (!application.capabilities.discover?.show) {
|
||||
const discoverNotEnabled = i18n.translate(
|
||||
'xpack.aiops.spikeAnalysisTable.discoverNotEnabledErrorMessage',
|
||||
{
|
||||
defaultMessage: 'Discover is not enabled',
|
||||
}
|
||||
);
|
||||
|
||||
return discoverNotEnabled;
|
||||
}
|
||||
if (!discoverLocator) {
|
||||
const discoverLocatorMissing = i18n.translate(
|
||||
'xpack.aiops.spikeAnalysisTable.discoverLocatorMissingErrorMessage',
|
||||
{
|
||||
defaultMessage: 'No locator for Discover detected',
|
||||
}
|
||||
);
|
||||
|
||||
return discoverLocatorMissing;
|
||||
}
|
||||
if (!dataViewId) {
|
||||
const autoGeneratedDiscoverLinkError = i18n.translate(
|
||||
'xpack.aiops.spikeAnalysisTable.autoGeneratedDiscoverLinkErrorMessage',
|
||||
{
|
||||
defaultMessage: 'Unable to link to Discover; no data view exists for this index',
|
||||
}
|
||||
);
|
||||
|
||||
return autoGeneratedDiscoverLinkError;
|
||||
}
|
||||
}, [application.capabilities.discover?.show, dataViewId, discoverLocator]);
|
||||
|
||||
const generateDiscoverUrl = async (changePoint: ChangePoint) => {
|
||||
if (discoverLocator !== undefined) {
|
||||
const url = await discoverLocator.getRedirectUrl({
|
||||
indexPatternId: dataViewId,
|
||||
timeRange: data.query.timefilter.timefilter.getTime(),
|
||||
filters: data.query.filterManager.getFilters(),
|
||||
query: {
|
||||
language: SEARCH_QUERY_LANGUAGE.KUERY,
|
||||
query: `${escapeKuery(changePoint.fieldName)}:${escapeKuery(
|
||||
String(changePoint.fieldValue)
|
||||
)}`,
|
||||
},
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<ChangePoint>> = [
|
||||
{
|
||||
'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldName',
|
||||
field: 'fieldName',
|
||||
name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldNameLabel', {
|
||||
defaultMessage: 'Field name',
|
||||
}),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'aiopsSpikeAnalysisTableColumnFieldValue',
|
||||
field: 'fieldValue',
|
||||
name: i18n.translate('xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.fieldValueLabel', {
|
||||
defaultMessage: 'Field value',
|
||||
}),
|
||||
render: (_, { fieldValue }) => String(fieldValue).slice(0, 50),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'aiopsSpikeAnalysisTableColumnLogRate',
|
||||
width: NARROW_COLUMN_WIDTH,
|
||||
field: 'pValue',
|
||||
name: (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.logRateColumnTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'A visual representation of the impact of the field on the message rate difference',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.logRateLabel"
|
||||
defaultMessage="Log rate"
|
||||
/>
|
||||
<EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" />
|
||||
</>
|
||||
</EuiToolTip>
|
||||
),
|
||||
render: (_, { histogram, fieldName, fieldValue }) => {
|
||||
if (!histogram) return NOT_AVAILABLE;
|
||||
return (
|
||||
<MiniHistogram
|
||||
chartData={histogram}
|
||||
isLoading={loading && histogram === undefined}
|
||||
label={`${fieldName}:${fieldValue}`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'aiopsSpikeAnalysisTableColumnPValue',
|
||||
width: NARROW_COLUMN_WIDTH,
|
||||
field: 'pValue',
|
||||
name: (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.pValueColumnTooltip',
|
||||
{
|
||||
defaultMessage:
|
||||
'The significance of changes in the frequency of values; lower values indicate greater change',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.pValueLabel"
|
||||
defaultMessage="p-value"
|
||||
/>
|
||||
<EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" />
|
||||
</>
|
||||
</EuiToolTip>
|
||||
),
|
||||
render: (pValue: number | null) => pValue?.toPrecision(3) ?? NOT_AVAILABLE,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'aiopsSpikeAnalysisTableColumnImpact',
|
||||
width: NARROW_COLUMN_WIDTH,
|
||||
field: 'pValue',
|
||||
name: (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate(
|
||||
'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.impactLabelColumnTooltip',
|
||||
{
|
||||
defaultMessage: 'The level of impact of the field on the message rate difference',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.impactLabel"
|
||||
defaultMessage="Impact"
|
||||
/>
|
||||
<EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" />
|
||||
</>
|
||||
</EuiToolTip>
|
||||
),
|
||||
render: (_, { pValue }) => {
|
||||
if (!pValue) return NOT_AVAILABLE;
|
||||
const label = getFailedTransactionsCorrelationImpactLabel(pValue);
|
||||
return label ? <EuiBadge color={label.color}>{label.impact}</EuiBadge> : null;
|
||||
},
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'aiOpsSpikeAnalysisTableColumnAction',
|
||||
name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
name: () => (
|
||||
<EuiToolTip content={discoverUrlError ? discoverUrlError : viewInDiscoverMessage}>
|
||||
<EuiIcon type="discoverApp" />
|
||||
</EuiToolTip>
|
||||
),
|
||||
description: viewInDiscoverMessage,
|
||||
type: 'button',
|
||||
onClick: async (changePoint) => {
|
||||
const openInDiscoverUrl = await generateDiscoverUrl(changePoint);
|
||||
if (typeof openInDiscoverUrl === 'string') {
|
||||
await application.navigateToUrl(openInDiscoverUrl);
|
||||
}
|
||||
},
|
||||
enabled: () => discoverUrlError === undefined,
|
||||
},
|
||||
],
|
||||
width: ACTIONS_COLUMN_WIDTH,
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = useCallback((tableSettings) => {
|
||||
const { index, size } = tableSettings.page;
|
||||
const { field, direction } = tableSettings.sort;
|
||||
|
||||
setPageIndex(index);
|
||||
setPageSize(size);
|
||||
setSortField(field);
|
||||
setSortDirection(direction);
|
||||
}, []);
|
||||
|
||||
const { pagination, pageOfItems, sorting } = useMemo(() => {
|
||||
const pageStart = pageIndex * pageSize;
|
||||
const itemCount = changePoints?.length ?? 0;
|
||||
|
||||
let items: ChangePoint[] = changePoints ?? [];
|
||||
items = sortBy(changePoints, (item) => {
|
||||
if (item && typeof item[sortField] === 'string') {
|
||||
// @ts-ignore Object is possibly null or undefined
|
||||
return item[sortField].toLowerCase();
|
||||
}
|
||||
return item[sortField];
|
||||
});
|
||||
items = sortDirection === 'asc' ? items : items.reverse();
|
||||
|
||||
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, changePoints]);
|
||||
|
||||
// Don't pass on the `loading` state to the table itself because
|
||||
// it disables hovering events. Because the mini histograms take a while
|
||||
// to load, hovering would not update the main chart. Instead,
|
||||
// the loading state is shown by the progress bar on the outer component level.
|
||||
// The outer component also will display a prompt when no data was returned
|
||||
// running the analysis and will hide this table.
|
||||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
data-test-subj="aiopsSpikeAnalysisTable"
|
||||
compressed
|
||||
columns={columns}
|
||||
items={pageOfItems}
|
||||
onChange={onChange}
|
||||
pagination={pagination}
|
||||
loading={false}
|
||||
sorting={sorting as EuiTableSortingType<ChangePoint>}
|
||||
rowProps={(changePoint) => {
|
||||
return {
|
||||
'data-test-subj': `aiopsSpikeAnalysisTableRow row-${changePoint.fieldName}-${changePoint.fieldValue}`,
|
||||
onClick: () => {
|
||||
if (onPinnedChangePoint) {
|
||||
onPinnedChangePoint(changePoint);
|
||||
}
|
||||
},
|
||||
onMouseEnter: () => {
|
||||
if (onSelectedChangePoint) {
|
||||
onSelectedChangePoint(changePoint);
|
||||
}
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
if (onSelectedChangePoint) {
|
||||
onSelectedChangePoint(null);
|
||||
}
|
||||
},
|
||||
style:
|
||||
selectedChangePoint &&
|
||||
selectedChangePoint.fieldValue === changePoint.fieldValue &&
|
||||
selectedChangePoint.fieldName === changePoint.fieldName
|
||||
? {
|
||||
backgroundColor: euiTheme.euiColorLightestShade,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
||||
import { useEuiTheme } from '../../hooks/use_eui_theme';
|
||||
import { SpikeAnalysisTableExpandedRow } from './spike_analysis_table_expanded_row';
|
||||
import { SpikeAnalysisTable } from './spike_analysis_table';
|
||||
|
||||
const NARROW_COLUMN_WIDTH = '120px';
|
||||
const EXPAND_COLUMN_WIDTH = '40px';
|
||||
|
@ -100,7 +100,7 @@ export const SpikeAnalysisGroupsTable: FC<SpikeAnalysisTableProps> = ({
|
|||
}
|
||||
|
||||
itemIdToExpandedRowMapValues[item.id] = (
|
||||
<SpikeAnalysisTableExpandedRow
|
||||
<SpikeAnalysisTable
|
||||
changePoints={expandedTableItems as ChangePoint[]}
|
||||
loading={loading}
|
||||
onPinnedChangePoint={onPinnedChangePoint}
|
||||
|
@ -127,7 +127,21 @@ export const SpikeAnalysisGroupsTable: FC<SpikeAnalysisTableProps> = ({
|
|||
<EuiButtonIcon
|
||||
data-test-subj={'aiopsSpikeAnalysisGroupsTableRowExpansionButton'}
|
||||
onClick={() => toggleDetails(item)}
|
||||
aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'}
|
||||
aria-label={
|
||||
itemIdToExpandedRowMap[item.id]
|
||||
? i18n.translate(
|
||||
'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.collapseAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Collapse',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.aiops.explainLogRateSpikes.spikeAnalysisTable.expandAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Expand',
|
||||
}
|
||||
)
|
||||
}
|
||||
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
|
||||
/>
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue