mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Security Solution] Event Renderer Virtualization (#193316)
## Summary This PR implements virtualization when Event Renderers are enabled. Ideally from UX pespective nothing should change but from performance perspective, the event renderers should be scalable. ### Testing checklist 1. UX is working same as before when Event Renderers are enabled. 2. Operations such as increasing page size from `10` to `100` are not taking as much time as before. Below operations can be used to test. a. Closing / Opening Timeline b. Changes `Rows per page` c. Changes tabs from query to any other and back. ### Before In below video, you will notice how long it took to change `pageSize` to 100 and all 100 rows are rendered at once. https://github.com/user-attachments/assets/106669c9-bda8-4b7d-af3f-b64824bde397 ### After https://github.com/user-attachments/assets/356d9e1f-caf1-4f88-9223-0e563939bf6b > [!Note] > 1. Also test in small screen. The table should be scrollable but nothing out of ordinary. > 2. Additionally, try to load data which has `network_flow` process so as to create bigger and varied Event Renderers. --------- Co-authored-by: Cee Chen <constance.chen@elastic.co>
This commit is contained in:
parent
2f76b60b0e
commit
fa92a8ede7
14 changed files with 491 additions and 265 deletions
|
@ -1632,7 +1632,7 @@
|
|||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-syntax-highlighter": "^15.4.0",
|
||||
"@types/react-test-renderer": "^17.0.2",
|
||||
"@types/react-virtualized": "^9.21.22",
|
||||
"@types/react-virtualized": "^9.21.30",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/react-window-infinite-loader": "^1.0.9",
|
||||
"@types/redux-actions": "^2.6.1",
|
||||
|
|
|
@ -513,3 +513,8 @@ export const CASE_ATTACHMENT_ENDPOINT_TYPE_ID = 'endpoint' as const;
|
|||
*/
|
||||
export const MAX_MANUAL_RULE_RUN_LOOKBACK_WINDOW_DAYS = 90;
|
||||
export const MAX_MANUAL_RULE_RUN_BULK_SIZE = 100;
|
||||
|
||||
/*
|
||||
* Whether it is a Jest environment
|
||||
*/
|
||||
export const JEST_ENVIRONMENT = typeof jest !== 'undefined';
|
||||
|
|
|
@ -40,69 +40,67 @@ import { useStatefulRowRenderer } from './use_stateful_row_renderer';
|
|||
* which focuses the current or next row, respectively.
|
||||
* - A screen-reader-only message provides additional context and instruction
|
||||
*/
|
||||
export const StatefulRowRenderer = ({
|
||||
ariaRowindex,
|
||||
containerRef,
|
||||
event,
|
||||
lastFocusedAriaColindex,
|
||||
rowRenderers,
|
||||
timelineId,
|
||||
}: {
|
||||
ariaRowindex: number;
|
||||
containerRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
event: TimelineItem;
|
||||
lastFocusedAriaColindex: number;
|
||||
rowRenderers: RowRenderer[];
|
||||
timelineId: string;
|
||||
}) => {
|
||||
const { focusOwnership, onFocus, onKeyDown, onOutsideClick } = useStatefulEventFocus({
|
||||
export const StatefulRowRenderer = React.memo(
|
||||
({
|
||||
ariaRowindex,
|
||||
colindexAttribute: ARIA_COLINDEX_ATTRIBUTE,
|
||||
containerRef,
|
||||
event,
|
||||
lastFocusedAriaColindex,
|
||||
onColumnFocused: noop,
|
||||
rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE,
|
||||
});
|
||||
|
||||
const { rowRenderer } = useStatefulRowRenderer({
|
||||
data: event.ecs,
|
||||
rowRenderers,
|
||||
});
|
||||
|
||||
const content = useMemo(
|
||||
() =>
|
||||
rowRenderer && (
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||
<div className={getRowRendererClassName(ariaRowindex)} role="dialog" onFocus={onFocus}>
|
||||
<EuiOutsideClickDetector onOutsideClick={onOutsideClick}>
|
||||
<EuiFocusTrap clickOutsideDisables={true} disabled={focusOwnership !== 'owned'}>
|
||||
<EuiScreenReaderOnly data-test-subj="eventRendererScreenReaderOnly">
|
||||
<p>{i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}</p>
|
||||
</EuiScreenReaderOnly>
|
||||
<EuiFlexGroup direction="column" onKeyDown={onKeyDown}>
|
||||
<EuiFlexItem grow={true}>
|
||||
{rowRenderer.renderRow({
|
||||
data: event.ecs,
|
||||
isDraggable: true,
|
||||
scopeId: timelineId,
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFocusTrap>
|
||||
</EuiOutsideClickDetector>
|
||||
</div>
|
||||
),
|
||||
[
|
||||
timelineId,
|
||||
}: {
|
||||
ariaRowindex: number;
|
||||
containerRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
event: TimelineItem;
|
||||
lastFocusedAriaColindex: number;
|
||||
rowRenderers: RowRenderer[];
|
||||
timelineId: string;
|
||||
}) => {
|
||||
const { focusOwnership, onFocus, onKeyDown, onOutsideClick } = useStatefulEventFocus({
|
||||
ariaRowindex,
|
||||
event.ecs,
|
||||
focusOwnership,
|
||||
onFocus,
|
||||
onKeyDown,
|
||||
onOutsideClick,
|
||||
rowRenderer,
|
||||
timelineId,
|
||||
]
|
||||
);
|
||||
colindexAttribute: ARIA_COLINDEX_ATTRIBUTE,
|
||||
containerRef,
|
||||
lastFocusedAriaColindex,
|
||||
onColumnFocused: noop,
|
||||
rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE,
|
||||
});
|
||||
|
||||
return content;
|
||||
};
|
||||
const { rowRenderer } = useStatefulRowRenderer({
|
||||
data: event.ecs,
|
||||
rowRenderers,
|
||||
});
|
||||
|
||||
const row = useMemo(() => {
|
||||
const result = rowRenderer?.renderRow({
|
||||
data: event.ecs,
|
||||
isDraggable: false,
|
||||
scopeId: timelineId,
|
||||
});
|
||||
return result;
|
||||
}, [rowRenderer, event.ecs, timelineId]);
|
||||
|
||||
const content = useMemo(
|
||||
() =>
|
||||
rowRenderer && (
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||
<div className={getRowRendererClassName(ariaRowindex)} role="dialog" onFocus={onFocus}>
|
||||
<EuiOutsideClickDetector onOutsideClick={onOutsideClick}>
|
||||
<EuiFocusTrap clickOutsideDisables={true} disabled={focusOwnership !== 'owned'}>
|
||||
<EuiScreenReaderOnly data-test-subj="eventRendererScreenReaderOnly">
|
||||
<p>{i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}</p>
|
||||
</EuiScreenReaderOnly>
|
||||
<EuiFlexGroup direction="column" onKeyDown={onKeyDown}>
|
||||
<EuiFlexItem grow={true}>{row}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFocusTrap>
|
||||
</EuiOutsideClickDetector>
|
||||
</div>
|
||||
),
|
||||
[ariaRowindex, focusOwnership, onFocus, onKeyDown, onOutsideClick, rowRenderer, row]
|
||||
);
|
||||
|
||||
return content;
|
||||
}
|
||||
);
|
||||
|
||||
StatefulRowRenderer.displayName = 'StatefulRowRenderer';
|
||||
|
|
|
@ -17,6 +17,7 @@ interface UseStatefulRowRendererArgs {
|
|||
|
||||
export function useStatefulRowRenderer(args: UseStatefulRowRendererArgs) {
|
||||
const { data, rowRenderers } = args;
|
||||
|
||||
const rowRenderer = useMemo(() => getRowRenderer({ data, rowRenderers }), [data, rowRenderers]);
|
||||
|
||||
const result = useMemo(
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
import type { SortColumnTable } from '@kbn/securitysolution-data-table';
|
||||
import type { TimelineItem } from '@kbn/timelines-plugin/common';
|
||||
import { JEST_ENVIRONMENT } from '../../../../../../common/constants';
|
||||
import { useLicense } from '../../../../../common/hooks/use_license';
|
||||
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
|
||||
import { useSourcererDataView } from '../../../../../sourcerer/containers';
|
||||
|
@ -21,6 +22,7 @@ import { TimelineControlColumnCellRender } from '../../unified_components/data_t
|
|||
import type { ColumnHeaderOptions } from '../../../../../../common/types';
|
||||
import { useTimelineColumns } from './use_timeline_columns';
|
||||
import type { UnifiedTimelineDataGridCellContext } from '../../types';
|
||||
import { useTimelineUnifiedDataTableContext } from '../../unified_components/data_table/use_timeline_unified_data_table_context';
|
||||
|
||||
interface UseTimelineControlColumnArgs {
|
||||
columns: ColumnHeaderOptions[];
|
||||
|
@ -59,6 +61,58 @@ export const useTimelineControlColumn = ({
|
|||
const ACTION_BUTTON_COUNT = isEnterprisePlus ? 6 : 5;
|
||||
const { localColumns } = useTimelineColumns(columns);
|
||||
|
||||
const RowCellRender = useMemo(
|
||||
() =>
|
||||
function TimelineControlColumnCellRenderer(
|
||||
props: EuiDataGridCellValueElementProps & UnifiedTimelineDataGridCellContext
|
||||
) {
|
||||
const ctx = useTimelineUnifiedDataTableContext();
|
||||
|
||||
useEffect(() => {
|
||||
props.setCellProps({
|
||||
className:
|
||||
ctx.expanded?.id === events[props.rowIndex]?._id
|
||||
? 'unifiedDataTable__cell--expanded'
|
||||
: '',
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* In some cases, when number of events is updated
|
||||
* but new table is not yet rendered it can result
|
||||
* in the mismatch between the number of events v/s
|
||||
* the number of rows in the table currently rendered.
|
||||
*
|
||||
* */
|
||||
if ('rowIndex' in props && props.rowIndex >= events.length) return <></>;
|
||||
return (
|
||||
<TimelineControlColumnCellRender
|
||||
rowIndex={props.rowIndex}
|
||||
columnId={props.columnId}
|
||||
timelineId={timelineId}
|
||||
ariaRowindex={props.rowIndex}
|
||||
checked={false}
|
||||
columnValues=""
|
||||
data={events[props.rowIndex].data}
|
||||
ecsData={events[props.rowIndex].ecs}
|
||||
loadingEventIds={EMPTY_STRING_ARRAY}
|
||||
eventId={events[props.rowIndex]?._id}
|
||||
index={props.rowIndex}
|
||||
onEventDetailsPanelOpened={noOp}
|
||||
onRowSelected={noOp}
|
||||
refetch={refetch}
|
||||
showCheckboxes={false}
|
||||
setEventsLoading={noOp}
|
||||
setEventsDeleted={noOp}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
eventIdToNoteIds={eventIdToNoteIds}
|
||||
toggleShowNotes={onToggleShowNotes}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[events, timelineId, refetch, pinnedEventIds, eventIdToNoteIds, onToggleShowNotes]
|
||||
);
|
||||
|
||||
// We need one less when the unified components are enabled because the document expand is provided by the unified data table
|
||||
const UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT = ACTION_BUTTON_COUNT - 1;
|
||||
return useMemo(() => {
|
||||
|
@ -84,49 +138,7 @@ export const useTimelineControlColumn = ({
|
|||
/>
|
||||
);
|
||||
},
|
||||
rowCellRender: (
|
||||
props: EuiDataGridCellValueElementProps & UnifiedTimelineDataGridCellContext
|
||||
) => {
|
||||
/*
|
||||
* In some cases, when number of events is updated
|
||||
* but new table is not yet rendered it can result
|
||||
* in the mismatch between the number of events v/s
|
||||
* the number of rows in the table currently rendered.
|
||||
*
|
||||
* */
|
||||
if ('rowIndex' in props && props.rowIndex >= events.length) return <></>;
|
||||
props.setCellProps({
|
||||
className:
|
||||
props.expandedEventId === events[props.rowIndex]?._id
|
||||
? 'unifiedDataTable__cell--expanded'
|
||||
: '',
|
||||
});
|
||||
|
||||
return (
|
||||
<TimelineControlColumnCellRender
|
||||
rowIndex={props.rowIndex}
|
||||
columnId={props.columnId}
|
||||
timelineId={timelineId}
|
||||
ariaRowindex={props.rowIndex}
|
||||
checked={false}
|
||||
columnValues=""
|
||||
data={events[props.rowIndex].data}
|
||||
ecsData={events[props.rowIndex].ecs}
|
||||
loadingEventIds={EMPTY_STRING_ARRAY}
|
||||
eventId={events[props.rowIndex]?._id}
|
||||
index={props.rowIndex}
|
||||
onEventDetailsPanelOpened={noOp}
|
||||
onRowSelected={noOp}
|
||||
refetch={refetch}
|
||||
showCheckboxes={false}
|
||||
setEventsLoading={noOp}
|
||||
setEventsDeleted={noOp}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
eventIdToNoteIds={eventIdToNoteIds}
|
||||
toggleShowNotes={onToggleShowNotes}
|
||||
/>
|
||||
);
|
||||
},
|
||||
rowCellRender: JEST_ENVIRONMENT ? RowCellRender : React.memo(RowCellRender),
|
||||
}));
|
||||
} else {
|
||||
return getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
|
||||
|
@ -142,11 +154,7 @@ export const useTimelineControlColumn = ({
|
|||
sort,
|
||||
activeTab,
|
||||
timelineId,
|
||||
refetch,
|
||||
events,
|
||||
pinnedEventIds,
|
||||
eventIdToNoteIds,
|
||||
onToggleShowNotes,
|
||||
ACTION_BUTTON_COUNT,
|
||||
RowCellRender,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -2,32 +2,35 @@
|
|||
|
||||
exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = `
|
||||
.c0 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
border-bottom: 1px solid 1px solid #343741;
|
||||
}
|
||||
|
||||
.c0 . euiDataGridRowCell--controlColumn {
|
||||
height: auto;
|
||||
min-height: 34px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-bottom: 1px solid #343741;
|
||||
}
|
||||
|
||||
.c0 .udt--customRow {
|
||||
border-radius: 0;
|
||||
padding: 6px;
|
||||
max-width: 1200px;
|
||||
width: 85vw;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.c0 .euiCommentEvent__body {
|
||||
background-color: #1d1e24;
|
||||
.c0 .euiDataGridRowCell--lastColumn.euiDataGridRowCell--controlColumn .euiDataGridRowCell__content {
|
||||
width: 1000px;
|
||||
max-width: 1000px;
|
||||
overflow-x: auto;
|
||||
-webkit-scrollbar-width: thin;
|
||||
-moz-scrollbar-width: thin;
|
||||
-ms-scrollbar-width: thin;
|
||||
scrollbar-width: thin;
|
||||
-webkit-scroll-padding: 0 0 0 0,;
|
||||
-moz-scroll-padding: 0 0 0 0,;
|
||||
-ms-scroll-padding: 0 0 0 0,;
|
||||
scroll-padding: 0 0 0 0,;
|
||||
}
|
||||
|
||||
.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--firstColumn,
|
||||
.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--lastColumn,
|
||||
.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--controlColumn,
|
||||
.c0:has(.unifiedDataTable__cell--expanded) .udt--customRow {
|
||||
.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--firstColumn,
|
||||
.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--lastColumn,
|
||||
.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--controlColumn,
|
||||
.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .udt--customRow {
|
||||
background-color: #2e2d25;
|
||||
}
|
||||
|
||||
|
@ -42,47 +45,76 @@ exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = `
|
|||
|
||||
<div>
|
||||
<div
|
||||
class="c0 euiDataGridRow "
|
||||
role="row"
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1 rawEvent rowCellWrapper rawEvent"
|
||||
role="row"
|
||||
data-eui="EuiAutoSizer"
|
||||
>
|
||||
<div>
|
||||
Cell-0-0
|
||||
<div
|
||||
class="variable__list"
|
||||
style="position: relative; height: 600px; width: 1000px; overflow: auto; will-change: transform; direction: ltr; scroll-padding: 0 0 0 0;"
|
||||
>
|
||||
<div
|
||||
class="custom__grid__rows--container"
|
||||
data-test-subj="customGridRowsContainer"
|
||||
style="height: 0px; width: 100%; position: relative;"
|
||||
>
|
||||
<div
|
||||
role="row"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="euiDataGridRow "
|
||||
role="row"
|
||||
>
|
||||
<div
|
||||
class="c1 rawEvent rowCellWrapper rawEvent"
|
||||
role="row"
|
||||
>
|
||||
<div>
|
||||
Cell-0-0
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-1
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-2
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="row"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="euiDataGridRow--striped euiDataGridRow euiDataGridRow--striped"
|
||||
role="row"
|
||||
>
|
||||
<div
|
||||
class="c1 rawEvent rowCellWrapper rawEvent"
|
||||
role="row"
|
||||
>
|
||||
<div>
|
||||
Cell-1-0
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-1
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-2
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-1
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-2
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Cell-0-3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 euiDataGridRow--striped euiDataGridRow euiDataGridRow--striped"
|
||||
role="row"
|
||||
>
|
||||
<div
|
||||
class="c1 rawEvent rowCellWrapper rawEvent"
|
||||
role="row"
|
||||
>
|
||||
<div>
|
||||
Cell-1-0
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-1
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-2
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Cell-1-3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -48,10 +48,10 @@ const defaultProps: CustomTimelineDataGridBodyProps = {
|
|||
visibleColumns: mockVisibleColumns,
|
||||
headerRow: <></>,
|
||||
footerRow: null,
|
||||
gridWidth: 0,
|
||||
gridWidth: 1000,
|
||||
};
|
||||
|
||||
const renderTestComponents = (props?: CustomTimelineDataGridBodyProps) => {
|
||||
const renderTestComponents = (props?: Partial<CustomTimelineDataGridBodyProps>) => {
|
||||
const finalProps = props ? { ...defaultProps, ...props } : defaultProps;
|
||||
|
||||
return render(
|
||||
|
@ -88,8 +88,15 @@ describe('CustomTimelineDataGridBody', () => {
|
|||
(useStatefulRowRenderer as jest.Mock).mockReturnValueOnce({
|
||||
canShowRowRenderer: true,
|
||||
});
|
||||
const { getByText, queryByText } = renderTestComponents();
|
||||
const { getByTestId, getByText, queryByText } = renderTestComponents();
|
||||
|
||||
expect(getByTestId('customGridRowsContainer')).toBeVisible();
|
||||
expect(queryByText('Cell-0-3')).toBeFalsy();
|
||||
expect(getByText('Cell-1-3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render grid if gridWidth is 0', () => {
|
||||
const { queryByTestId } = renderTestComponents({ gridWidth: 0 });
|
||||
expect(queryByTestId('customGridRowsContainer')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,18 +5,24 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiDataGridCustomBodyProps } from '@elastic/eui';
|
||||
import type { EuiDataGridCustomBodyProps, EuiDataGridRowHeightsOptions } from '@elastic/eui';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import type { EuiTheme } from '@kbn/react-kibana-context-styled';
|
||||
import { type EuiTheme } from '@kbn/react-kibana-context-styled';
|
||||
import type { TimelineItem } from '@kbn/timelines-plugin/common';
|
||||
import type { FC } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import type { CSSProperties, FC, PropsWithChildren } from 'react';
|
||||
import React, { memo, useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import { EuiAutoSizer, useEuiTheme } from '@elastic/eui';
|
||||
import type { RowRenderer } from '../../../../../../common/types';
|
||||
import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants';
|
||||
import { useStatefulRowRenderer } from '../../body/events/stateful_row_renderer/use_stateful_row_renderer';
|
||||
import { getEventTypeRowClassName } from './get_event_type_row_classname';
|
||||
|
||||
const defaultAutoHeight: EuiDataGridRowHeightsOptions = {
|
||||
defaultHeight: 'auto',
|
||||
};
|
||||
|
||||
export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
|
||||
rows: Array<DataTableRecord & TimelineItem> | undefined;
|
||||
enabledRowRenderers: RowRenderer[];
|
||||
|
@ -24,9 +30,46 @@ export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
|
|||
refetch?: () => void;
|
||||
};
|
||||
|
||||
const VirtualizedCustomDataGridContainer = styled.div<{
|
||||
$maxWidth?: number;
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-bottom: ${(props) => (props.theme as EuiTheme).eui.euiBorderThin};
|
||||
.udt--customRow {
|
||||
border-radius: 0;
|
||||
padding: ${(props) => (props.theme as EuiTheme).eui.euiDataGridCellPaddingM};
|
||||
max-width: ${(props) => props.$maxWidth}px;
|
||||
}
|
||||
|
||||
.euiDataGridRowCell--lastColumn.euiDataGridRowCell--controlColumn .euiDataGridRowCell__content {
|
||||
width: ${(props) => props.$maxWidth}px;
|
||||
max-width: ${(props) => props.$maxWidth}px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
scroll-padding: 0 0 0 0,
|
||||
}
|
||||
|
||||
.euiDataGridRow:has(.unifiedDataTable__cell--expanded) {
|
||||
.euiDataGridRowCell--firstColumn,
|
||||
.euiDataGridRowCell--lastColumn,
|
||||
.euiDataGridRowCell--controlColumn,
|
||||
.udt--customRow {
|
||||
${({ theme }) => `background-color: ${theme.eui.euiColorHighlight};`}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// THE DataGrid Row default is 34px, but we make ours 40 to account for our row actions
|
||||
const DEFAULT_UDT_ROW_HEIGHT = 34;
|
||||
|
||||
const SCROLLBAR_STYLE: CSSProperties = {
|
||||
scrollbarWidth: 'thin',
|
||||
scrollPadding: '0 0 0 0',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* In order to render the additional row with every event ( which displays the row-renderer, notes and notes editor)
|
||||
|
@ -44,40 +87,170 @@ export const CustomTimelineDataGridBody: FC<CustomTimelineDataGridBodyProps> = m
|
|||
function CustomTimelineDataGridBody(props) {
|
||||
const {
|
||||
Cell,
|
||||
headerRow,
|
||||
footerRow,
|
||||
visibleColumns,
|
||||
visibleRowData,
|
||||
rows,
|
||||
rowHeight,
|
||||
enabledRowRenderers,
|
||||
refetch,
|
||||
setCustomGridBodyProps,
|
||||
headerRow,
|
||||
footerRow,
|
||||
gridWidth,
|
||||
} = props;
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
// // Set custom props onto the grid body wrapper
|
||||
const bodyRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
setCustomGridBodyProps({
|
||||
ref: bodyRef,
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflowY: 'hidden',
|
||||
scrollbarColor: `${euiTheme.colors.mediumShade} ${euiTheme.colors.lightestShade}`,
|
||||
},
|
||||
});
|
||||
}, [setCustomGridBodyProps, euiTheme.colors.mediumShade, euiTheme.colors.lightestShade]);
|
||||
|
||||
const visibleRows = useMemo(
|
||||
() => (rows ?? []).slice(visibleRowData.startRow, visibleRowData.endRow),
|
||||
[rows, visibleRowData]
|
||||
);
|
||||
|
||||
const listRef = useRef<VariableSizeList<unknown>>(null);
|
||||
|
||||
const rowHeights = useRef<number[]>([]);
|
||||
|
||||
const setRowHeight = useCallback((index: number, height: number) => {
|
||||
if (rowHeights.current[index] === height) return;
|
||||
listRef.current?.resetAfterIndex(index);
|
||||
|
||||
rowHeights.current[index] = height;
|
||||
}, []);
|
||||
|
||||
const getRowHeight = useCallback((index: number) => {
|
||||
return rowHeights.current[index] ?? 100;
|
||||
}, []);
|
||||
|
||||
/*
|
||||
*
|
||||
* There is a difference between calculatedWidth & gridWidth
|
||||
*
|
||||
* gridWidth is the width of the grid as per the screen size
|
||||
*
|
||||
* calculatedWidth is the width of the grid that is calculated by EUI and represents
|
||||
* the actual width of the grid based on the content of the grid. ( Sum of the width of all columns)
|
||||
*
|
||||
* For example, screensize can be variable but calculatedWidth can be much more than that
|
||||
* with grid having a horizontal scrollbar
|
||||
*
|
||||
*
|
||||
* */
|
||||
const [calculatedWidth, setCalculatedWidth] = useState<number>(gridWidth);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
* Any time gridWidth(available screen size) is changed, we need to re-check
|
||||
* to see if EUI has changed the width of the grid
|
||||
*
|
||||
*/
|
||||
if (!bodyRef) return;
|
||||
const headerRowRef = bodyRef?.current?.querySelector('.euiDataGridHeader[role="row"]');
|
||||
setCalculatedWidth((prev) =>
|
||||
headerRowRef?.clientWidth && headerRowRef?.clientWidth !== prev
|
||||
? headerRowRef?.clientWidth
|
||||
: prev
|
||||
);
|
||||
}, [gridWidth]);
|
||||
|
||||
const innerRowContainer = useMemo(() => {
|
||||
const InnerComp = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<{ style: CSSProperties }>
|
||||
>(({ children, style, ...rest }, ref) => {
|
||||
return (
|
||||
<>
|
||||
{headerRow}
|
||||
<div
|
||||
className="custom__grid__rows--container"
|
||||
data-test-subj="customGridRowsContainer"
|
||||
ref={ref}
|
||||
style={{ ...style, position: 'relative' }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{footerRow}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
InnerComp.displayName = 'InnerRowContainer';
|
||||
|
||||
return React.memo(InnerComp);
|
||||
}, [headerRow, footerRow]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerRow}
|
||||
{visibleRows.map((row, rowIndex) => {
|
||||
return (
|
||||
<CustomDataGridSingleRow
|
||||
rowData={row}
|
||||
rowIndex={rowIndex}
|
||||
key={rowIndex}
|
||||
visibleColumns={visibleColumns}
|
||||
rowHeight={rowHeight}
|
||||
Cell={Cell}
|
||||
enabledRowRenderers={enabledRowRenderers}
|
||||
refetch={refetch}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{footerRow}
|
||||
</>
|
||||
<VirtualizedCustomDataGridContainer $maxWidth={calculatedWidth}>
|
||||
<EuiAutoSizer className="autosizer" disableWidth>
|
||||
{({ height }) => {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
/**
|
||||
* whenever timeline is minimized, VariableList is re-rendered which causes delay,
|
||||
* so below code makes sure that grid is only rendered when gridWidth is not 0
|
||||
*/
|
||||
gridWidth !== 0 && (
|
||||
<>
|
||||
<VariableSizeList
|
||||
className="variable__list"
|
||||
/* available space on the screen */
|
||||
width={gridWidth}
|
||||
height={height}
|
||||
itemCount={visibleRows.length}
|
||||
itemSize={getRowHeight}
|
||||
overscanCount={5}
|
||||
ref={listRef}
|
||||
style={SCROLLBAR_STYLE}
|
||||
innerElementType={innerRowContainer}
|
||||
>
|
||||
{({ index, style }) => {
|
||||
return (
|
||||
<div
|
||||
role="row"
|
||||
style={{
|
||||
...style,
|
||||
width: 'fit-content',
|
||||
}}
|
||||
key={`${gridWidth}-${index}`}
|
||||
>
|
||||
<CustomDataGridSingleRow
|
||||
rowData={visibleRows[index]}
|
||||
rowIndex={index}
|
||||
visibleColumns={visibleColumns}
|
||||
Cell={Cell}
|
||||
enabledRowRenderers={enabledRowRenderers}
|
||||
refetch={refetch}
|
||||
setRowHeight={setRowHeight}
|
||||
rowHeight={rowHeight}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</VariableSizeList>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</EuiAutoSizer>
|
||||
</VirtualizedCustomDataGridContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -85,41 +258,17 @@ export const CustomTimelineDataGridBody: FC<CustomTimelineDataGridBodyProps> = m
|
|||
/**
|
||||
*
|
||||
* A Simple Wrapper component for displaying a custom grid row
|
||||
* Generating CSS on this row puts a huge performance overhead on the grid as each row much styled individually.
|
||||
* If possible, try to use the styles either in ../styles.tsx or in the parent component
|
||||
*
|
||||
*/
|
||||
|
||||
const CustomGridRow = styled.div.attrs<{
|
||||
className?: string;
|
||||
}>((props) => ({
|
||||
className: `euiDataGridRow ${props.className ?? ''}`,
|
||||
role: 'row',
|
||||
}))`
|
||||
width: fit-content;
|
||||
border-bottom: 1px solid ${(props) => (props.theme as EuiTheme).eui.euiBorderThin};
|
||||
. euiDataGridRowCell--controlColumn {
|
||||
height: ${(props: { $cssRowHeight: string }) => props.$cssRowHeight};
|
||||
min-height: ${DEFAULT_UDT_ROW_HEIGHT}px;
|
||||
}
|
||||
.udt--customRow {
|
||||
border-radius: 0;
|
||||
padding: ${(props) => (props.theme as EuiTheme).eui.euiDataGridCellPaddingM};
|
||||
max-width: ${(props) => (props.theme as EuiTheme).eui.euiPageDefaultMaxWidth};
|
||||
width: 85vw;
|
||||
}
|
||||
|
||||
.euiCommentEvent__body {
|
||||
background-color: ${(props) => (props.theme as EuiTheme).eui.euiColorEmptyShade};
|
||||
}
|
||||
|
||||
&:has(.unifiedDataTable__cell--expanded) {
|
||||
.euiDataGridRowCell--firstColumn,
|
||||
.euiDataGridRowCell--lastColumn,
|
||||
.euiDataGridRowCell--controlColumn,
|
||||
.udt--customRow {
|
||||
${({ theme }) => `background-color: ${theme.eui.euiColorHighlight};`}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}))``;
|
||||
|
||||
/* below styles as per : https://eui.elastic.co/#/tabular-content/data-grid-advanced#custom-body-renderer */
|
||||
const CustomGridRowCellWrapper = styled.div.attrs<{
|
||||
|
@ -138,6 +287,7 @@ const CustomGridRowCellWrapper = styled.div.attrs<{
|
|||
type CustomTimelineDataGridSingleRowProps = {
|
||||
rowData: DataTableRecord & TimelineItem;
|
||||
rowIndex: number;
|
||||
setRowHeight: (index: number, height: number) => void;
|
||||
} & Pick<
|
||||
CustomTimelineDataGridBodyProps,
|
||||
'visibleColumns' | 'Cell' | 'enabledRowRenderers' | 'refetch' | 'rowHeight'
|
||||
|
@ -168,13 +318,24 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
|
|||
visibleColumns,
|
||||
Cell,
|
||||
rowHeight: rowHeightMultiple = 0,
|
||||
setRowHeight,
|
||||
} = props;
|
||||
|
||||
const { canShowRowRenderer } = useStatefulRowRenderer({
|
||||
data: rowData.ecs,
|
||||
rowRenderers: enabledRowRenderers,
|
||||
});
|
||||
|
||||
const rowRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (rowRef.current) {
|
||||
setRowHeight(rowIndex, rowRef.current.offsetHeight);
|
||||
}
|
||||
}, [rowIndex, setRowHeight]);
|
||||
|
||||
const cssRowHeight: string = calculateRowHeightInPixels(rowHeightMultiple - 1);
|
||||
|
||||
/**
|
||||
* removes the border between the actual row ( timelineEvent) and `TimelineEventDetail` row
|
||||
* which renders the row-renderer, notes and notes editor
|
||||
|
@ -194,12 +355,11 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
|
|||
return (
|
||||
<CustomGridRow
|
||||
className={`${rowIndex % 2 !== 0 ? 'euiDataGridRow--striped' : ''}`}
|
||||
$cssRowHeight={cssRowHeight}
|
||||
key={rowIndex}
|
||||
ref={rowRef}
|
||||
>
|
||||
<CustomGridRowCellWrapper className={eventTypeRowClassName} $cssRowHeight={cssRowHeight}>
|
||||
{visibleColumns.map((column, colIndex) => {
|
||||
// Skip the expanded row cell - we'll render it manually outside of the flex wrapper
|
||||
if (column.id !== TIMELINE_EVENT_DETAIL_ROW_ID) {
|
||||
return (
|
||||
<Cell
|
||||
|
@ -217,6 +377,9 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
|
|||
{/* Timeline Expanded Row */}
|
||||
{canShowRowRenderer ? (
|
||||
<Cell
|
||||
rowHeightsOptions={defaultAutoHeight}
|
||||
/* @ts-expect-error because currently CellProps do not allow string width but it is important to be passed for height calculations */
|
||||
width={'100%'}
|
||||
colIndex={visibleColumns.length - 1} // If the row is being shown, it should always be the last index
|
||||
visibleRowIndex={rowIndex}
|
||||
/>
|
||||
|
|
|
@ -12,8 +12,13 @@ import type { DataTableRecord } from '@kbn/discover-utils/types';
|
|||
import type { UnifiedDataTableProps } from '@kbn/unified-data-table';
|
||||
import { UnifiedDataTable, DataLoadingState } from '@kbn/unified-data-table';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { EuiDataGridCustomBodyProps, EuiDataGridProps } from '@elastic/eui';
|
||||
import type {
|
||||
EuiDataGridControlColumn,
|
||||
EuiDataGridCustomBodyProps,
|
||||
EuiDataGridProps,
|
||||
} from '@elastic/eui';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { JEST_ENVIRONMENT } from '../../../../../../common/constants';
|
||||
import { useOnExpandableFlyoutClose } from '../../../../../flyout/shared/hooks/use_on_expandable_flyout_close';
|
||||
import { DocumentDetailsRightPanelKey } from '../../../../../flyout/document_details/shared/constants/panel_keys';
|
||||
import { selectTimelineById } from '../../../../store/selectors';
|
||||
|
@ -43,7 +48,6 @@ import { transformTimelineItemToUnifiedRows } from '../utils';
|
|||
import { TimelineEventDetailRow } from './timeline_event_detail_row';
|
||||
import { CustomTimelineDataGridBody } from './custom_timeline_data_grid_body';
|
||||
import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants';
|
||||
import type { UnifiedTimelineDataGridCellContext } from '../../types';
|
||||
|
||||
export const SAMPLE_SIZE_SETTING = 500;
|
||||
const DataGridMemoized = React.memo(UnifiedDataTable);
|
||||
|
@ -288,6 +292,23 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
return rowRenderers.filter((rowRenderer) => !excludedRowRendererIds.includes(rowRenderer.id));
|
||||
}, [excludedRowRendererIds, rowRenderers]);
|
||||
|
||||
const TimelineEventDetailRowRendererComp = useMemo<EuiDataGridControlColumn['rowCellRender']>(
|
||||
() =>
|
||||
function TimelineEventDetailRowRenderer(props) {
|
||||
const { rowIndex, ...restProps } = props;
|
||||
return (
|
||||
<TimelineEventDetailRow
|
||||
event={tableRows[rowIndex]}
|
||||
rowIndex={rowIndex}
|
||||
timelineId={timelineId}
|
||||
enabledRowRenderers={enabledRowRenderers}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[tableRows, timelineId, enabledRowRenderers]
|
||||
);
|
||||
|
||||
/**
|
||||
* Ref: https://eui.elastic.co/#/tabular-content/data-grid-advanced#custom-body-renderer
|
||||
*/
|
||||
|
@ -295,31 +316,20 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
() => [
|
||||
{
|
||||
id: TIMELINE_EVENT_DETAIL_ROW_ID,
|
||||
// The header cell should be visually hidden, but available to screen readers
|
||||
width: 0,
|
||||
// The header cell should be visually hidden, but available to screen readers
|
||||
headerCellRender: () => <></>,
|
||||
headerCellProps: { className: 'euiScreenReaderOnly' },
|
||||
|
||||
// The footer cell can be hidden to both visual & SR users, as it does not contain meaningful information
|
||||
footerCellProps: { style: { display: 'none' } },
|
||||
|
||||
// When rendering this custom cell, we'll want to override
|
||||
// the automatic width/heights calculated by EuiDataGrid
|
||||
rowCellRender: (props) => {
|
||||
const { rowIndex, ...restProps } = props;
|
||||
return (
|
||||
<TimelineEventDetailRow
|
||||
event={tableRows[rowIndex]}
|
||||
rowIndex={rowIndex}
|
||||
timelineId={timelineId}
|
||||
enabledRowRenderers={enabledRowRenderers}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
},
|
||||
rowCellRender: JEST_ENVIRONMENT
|
||||
? TimelineEventDetailRowRendererComp
|
||||
: React.memo(TimelineEventDetailRowRendererComp),
|
||||
},
|
||||
],
|
||||
[enabledRowRenderers, tableRows, timelineId]
|
||||
[TimelineEventDetailRowRendererComp]
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -352,12 +362,6 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
[tableRows, enabledRowRenderers, rowHeight, refetch]
|
||||
);
|
||||
|
||||
const cellContext: UnifiedTimelineDataGridCellContext = useMemo(() => {
|
||||
return {
|
||||
expandedEventId: expandedDoc?.id,
|
||||
};
|
||||
}, [expandedDoc]);
|
||||
|
||||
const finalRenderCustomBodyCallback = useMemo(() => {
|
||||
return enabledRowRenderers.length > 0 ? renderCustomBodyCallback : undefined;
|
||||
}, [enabledRowRenderers.length, renderCustomBodyCallback]);
|
||||
|
@ -419,7 +423,6 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
renderCustomGridBody={finalRenderCustomBodyCallback}
|
||||
trailingControlColumns={finalTrailControlColumns}
|
||||
externalControlColumns={leadingControlColumns}
|
||||
cellContext={cellContext}
|
||||
/>
|
||||
</StyledTimelineUnifiedDataTable>
|
||||
</StatefulEventContext.Provider>
|
||||
|
|
|
@ -18,7 +18,14 @@ const mockData = structuredClone(mockTimelineData);
|
|||
|
||||
const setCellPropsMock = jest.fn();
|
||||
|
||||
jest.mock('../../body/events/stateful_row_renderer');
|
||||
jest.mock('../../body/events/stateful_row_renderer', () => {
|
||||
return {
|
||||
StatefulRowRenderer: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const StatefulRowRendererMock = StatefulRowRenderer as unknown as jest.Mock;
|
||||
|
||||
jest.mock('./use_timeline_unified_data_table_context');
|
||||
|
||||
const renderTestComponent = (props: Partial<TimelineEventDetailRowProps> = {}) => {
|
||||
|
@ -44,7 +51,7 @@ const renderTestComponent = (props: Partial<TimelineEventDetailRowProps> = {}) =
|
|||
|
||||
describe('TimelineEventDetailRow', () => {
|
||||
beforeEach(() => {
|
||||
(StatefulRowRenderer as jest.Mock).mockReturnValue(<div>{'Test Row Renderer'}</div>);
|
||||
StatefulRowRendererMock.mockReturnValue(<div>{'Test Row Renderer'}</div>);
|
||||
|
||||
(useTimelineUnifiedDataTableContext as jest.Mock).mockReturnValue({
|
||||
expanded: { id: undefined },
|
||||
|
@ -60,7 +67,7 @@ describe('TimelineEventDetailRow', () => {
|
|||
|
||||
expect(setCellPropsMock).toHaveBeenCalledWith({
|
||||
className: '',
|
||||
style: { width: '100%', height: 'auto' },
|
||||
style: { width: '100%', height: undefined, overflowX: 'auto' },
|
||||
});
|
||||
|
||||
expect(getByText('Test Row Renderer')).toBeVisible();
|
||||
|
@ -82,7 +89,7 @@ describe('TimelineEventDetailRow', () => {
|
|||
|
||||
expect(setCellPropsMock).toHaveBeenCalledWith({
|
||||
className: 'unifiedDataTable__cell--expanded',
|
||||
style: { width: '100%', height: 'auto' },
|
||||
style: { width: '100%', height: undefined, overflowX: 'auto' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,7 +60,7 @@ export const TimelineEventDetailRow: React.FC<TimelineEventDetailRowProps> = mem
|
|||
useEffect(() => {
|
||||
setCellProps?.({
|
||||
className: ctx.expanded?.id === event._id ? 'unifiedDataTable__cell--expanded' : '',
|
||||
style: { width: '100%', height: 'auto' },
|
||||
style: { width: '100%', height: undefined, overflowX: 'auto' },
|
||||
});
|
||||
}, [ctx.expanded?.id, setCellProps, rowIndex, event._id]);
|
||||
|
||||
|
@ -72,7 +72,7 @@ export const TimelineEventDetailRow: React.FC<TimelineEventDetailRowProps> = mem
|
|||
alignItems="center"
|
||||
data-test-subj={`timeline-row-renderer-${rowIndex}`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EventsTrSupplement>
|
||||
<StatefulRowRenderer
|
||||
ariaRowindex={rowIndex + ARIA_ROW_INDEX_OFFSET}
|
||||
|
|
|
@ -66,6 +66,11 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = ''
|
|||
className: `unifiedDataTable ${className}`,
|
||||
role: 'rowgroup',
|
||||
}))`
|
||||
.udtTimeline .euiDataGrid__virtualized {
|
||||
${({ theme }) =>
|
||||
`scrollbar-color: ${theme.eui.euiColorMediumShade} ${theme.eui.euiColorLightShade}`};
|
||||
}
|
||||
|
||||
.udtTimeline [data-gridcell-column-id|='select'] {
|
||||
border-right: none;
|
||||
}
|
||||
|
@ -182,6 +187,10 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = ''
|
|||
align-items: baseline;
|
||||
}
|
||||
|
||||
.euiDataGrid__customRenderBody {
|
||||
scrollbar-color: transparent !important;
|
||||
}
|
||||
|
||||
${leadingActionsColumnStyles}
|
||||
`;
|
||||
|
||||
|
|
|
@ -503,8 +503,6 @@ describe('indicator match', { tags: ['@ess', '@serverless', '@skipInServerlessMK
|
|||
});
|
||||
|
||||
it('Investigate alert in timeline', () => {
|
||||
const accessibilityText = `Press enter for options, or press space to begin dragging.`;
|
||||
|
||||
loadPrepackagedTimelineTemplates();
|
||||
createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then(
|
||||
(rule) => visitRuleDetailsPage(rule.body.id)
|
||||
|
@ -525,14 +523,9 @@ describe('indicator match', { tags: ['@ess', '@serverless', '@skipInServerlessMK
|
|||
|
||||
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
|
||||
'have.text',
|
||||
`threat.enrichments.matched.field${
|
||||
getNewThreatIndicatorRule().threat_mapping[0].entries[0].field
|
||||
}${accessibilityText}matched${
|
||||
getNewThreatIndicatorRule().threat_mapping[0].entries[0].field
|
||||
}${
|
||||
`${getNewThreatIndicatorRule().threat_mapping[0].entries[0].field}matched${
|
||||
indicatorRuleMatchingDoc.atomic
|
||||
}${accessibilityText}threat.enrichments.matched.typeindicator_match_rule${accessibilityText}provided` +
|
||||
` byfeed.nameAbuseCH malware${accessibilityText}`
|
||||
}indicator_match_ruleprovided` + ` byAbuseCH malware`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11316,10 +11316,10 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-virtualized@^9.21.22":
|
||||
version "9.21.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.22.tgz#5ba39b29869200620a6bf2069b8393f258a9c1e2"
|
||||
integrity sha512-YRifyCKnBG84+J/Hny0f3bo8BRrcNT74CvsAVpQpZcS83fdC7lP7RfzwL2ND8/ihhpnDFL1IbxJ9MpQNaKUDuQ==
|
||||
"@types/react-virtualized@^9.21.30":
|
||||
version "9.21.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.30.tgz#ba39821bcb2487512a8a2cdd9fbdb5e6fc87fedb"
|
||||
integrity sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/react" "*"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue