[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:
Melissa Alvarez 2022-09-20 11:37:31 -06:00 committed by GitHub
parent f4dd515641
commit 130a8cde54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 18 additions and 358 deletions

View file

@ -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;
},

View file

@ -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,
};
}}
/>
);
};

View file

@ -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'}
/>
),