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

[Security Solution] Event Renderer Virtualization #193316

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
697974b
fix: virt. working with height issue
logeekal Sep 11, 2024
0321968
fix: working list virt - react-virtualized
logeekal Sep 16, 2024
f35f347
react-window virtualization
logeekal Sep 18, 2024
5ca5a77
incremental changes
logeekal Sep 19, 2024
c1a79ea
Great pert with auto height.
logeekal Sep 25, 2024
cb98169
fix: working great + better performance
logeekal Sep 27, 2024
84de4ee
fix: fast and working RC
logeekal Sep 27, 2024
962d7ad
correct deps
logeekal Sep 27, 2024
9a7efb2
eui_rc
logeekal Sep 30, 2024
f080311
Upgrade EUI to v97.0.0
cee-chen Oct 9, 2024
5495557
i18n updates
cee-chen Oct 9, 2024
6f99866
[EuiDataGrid] Handle breaking `renderCustomGridBody` change for Alert…
cee-chen Oct 9, 2024
22d78d6
[EuiDataGrid] Handle breaking `renderCustomGridBody` change for Secur…
cee-chen Oct 9, 2024
657a043
[EuiLink] Update snapshots to account for new SR text copy
cee-chen Oct 9, 2024
fbf95d4
[EuiLink] Update text assertions/selectors to account for new SR text…
cee-chen Oct 9, 2024
3bf6b59
[EuiLink] Update test regex utils to account for new SR text copy
cee-chen Oct 9, 2024
a03923d
[EuiLink] Update test icon assertion to account for the fact the icon…
cee-chen Oct 9, 2024
d049f3b
[EuiButtonDisplay] Update snapshots
cee-chen Oct 9, 2024
924f71f
Merge remote-tracking branch 'cee/eui/v97.0.0' into feat/custom_grid_…
logeekal Oct 10, 2024
f87fbea
fix: more optims
logeekal Oct 11, 2024
7e65a87
remove unnecessary logic
logeekal Oct 11, 2024
72cc0d6
fix: jest tests
logeekal Oct 14, 2024
a43664d
Merge branch 'main' into feat/custom_grid_body_list_virtualization
logeekal Oct 15, 2024
7399da6
chore: remove unnecessary console statements
logeekal Oct 15, 2024
5c089b0
ci: 😊
logeekal Oct 15, 2024
2eb53a0
Merge branch 'main' into feat/custom_grid_body_list_virtualization
logeekal Oct 16, 2024
12f6032
fix: cypress test
logeekal Oct 16, 2024
a64ccc4
fix: remove focused test
logeekal Oct 16, 2024
fd9e1ef
fix: types
logeekal Oct 16, 2024
fcdbaf0
Merge branch 'main' into feat/custom_grid_body_list_virtualization
logeekal Oct 16, 2024
86e5d00
Merge branch 'main' into feat/custom_grid_body_list_virtualization
logeekal Oct 16, 2024
ecd7653
Merge branch 'main' into feat/custom_grid_body_list_virtualization
logeekal Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"@elastic/ecs": "^8.11.1",
"@elastic/elasticsearch": "^8.15.0",
"@elastic/ems-client": "8.5.3",
"@elastic/eui": "95.10.1",
"@elastic/eui": "/Users/jatinkathuria/projects/eui/packages/eui/elastic-eui-95.10.1_rc1.tgz",
"@elastic/filesaver": "1.1.2",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.1",
Expand Down Expand Up @@ -1598,7 +1598,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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ export const UnifiedDataTableAdditionalDisplaySettings: React.FC<
if (onChangeSampleSize) {
let step = minRangeSampleSize === RANGE_MIN_SAMPLE_SIZE ? RANGE_STEP_SAMPLE_SIZE : 1;

console.log({
activeSampleSize,
maxAllowedSampleSize,
minRangeSampleSize,
});
if (
step > 1 &&
((activeSampleSize && !checkIfValueIsMultipleOfStep(activeSampleSize, step)) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const StatefulRowRenderer = ({
<EuiFlexItem grow={true}>
{rowRenderer.renderRow({
data: event.ecs,
isDraggable: true,
isDraggable: false,
scopeId: timelineId,
})}
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ interface UseStatefulRowRendererArgs {

export function useStatefulRowRenderer(args: UseStatefulRowRendererArgs) {
const { data, rowRenderers } = args;

console.time('getRowRenderer');
const rowRenderer = useMemo(() => getRowRenderer({ data, rowRenderers }), [data, rowRenderers]);
console.timeEnd('getRowRenderer');

const result = useMemo(
() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const compareQueryProps = (prevProps: Props, nextProps: Props) =>

export type Props = TimelineTabCommonProps & PropsFromRedux;

const PAGE_OPTIONS = [10, 100, 1000];

export const QueryTabContentComponent: React.FC<Props> = ({
activeTab,
columns,
Expand Down Expand Up @@ -521,7 +523,7 @@ const makeMapStateToProps = () => {
eventIdToNoteIds,
isLive: input.policy.kind === 'interval',
itemsPerPage,
itemsPerPageOptions,
itemsPerPageOptions: PAGE_OPTIONS,
kqlMode,
kqlQueryExpression,
kqlQueryLanguage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@

import type { EuiDataGridCustomBodyProps } 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 } 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 = {
defaultHeight: 'auto',
};

export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
rows: Array<DataTableRecord & TimelineItem> | undefined;
enabledRowRenderers: RowRenderer[];
Expand All @@ -27,6 +33,12 @@ export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
// 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)
Expand All @@ -42,31 +54,137 @@ const DEFAULT_UDT_ROW_HEIGHT = 34;
* */
export const CustomTimelineDataGridBody: FC<CustomTimelineDataGridBodyProps> = memo(
function CustomTimelineDataGridBody(props) {
const { Cell, visibleColumns, visibleRowData, rows, rowHeight, enabledRowRenderers, refetch } =
props;
const {
Cell,
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;
}, []);

const innerRowContainer = useMemo(() => {
const InnerComp = React.forwardRef<HTMLDivElement, PropsWithChildren<{}>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

can probably pull this definition to the top level of this file, and that way this is never recreated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes.. I was just waiting for the EUI teams opinion and then i will refactor code to be put in production. Currently, this is just working code.

I wanted to get everyone opinions mainly on performance. But what you say makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kqualters-elastic , I just checked, it has depenendencies headerRow and footerRow and hence it cannot be pulled out.

({ children, style, ...rest }, ref) => {
return (
<>
{headerRow}
<div
className="row-container"
ref={ref}
style={{ ...style, position: 'relative' }}
{...rest}
>
{children}
</div>

{footerRow}
</>
);
}
);

InnerComp.displayName = 'InnerRowContainer';

return InnerComp;
}, [headerRow, footerRow]);

return (
<>
{visibleRows.map((row, rowIndex) => {
<EuiAutoSizer className="autosizer" disableWidth>
{({ height }) => {
return (
<CustomDataGridSingleRow
rowData={row}
rowIndex={rowIndex}
key={rowIndex}
visibleColumns={visibleColumns}
rowHeight={rowHeight}
Cell={Cell}
enabledRowRenderers={enabledRowRenderers}
refetch={refetch}
/>
<>
{
/**
* whenever timeline is minimized, Variable 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"
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}
maxWidth={gridWidth}
/>
</div>
);
}}
</VariableSizeList>
</>
)
}
</>
);
})}
</>
}}
</EuiAutoSizer>
);
}
);
Expand All @@ -81,13 +199,10 @@ const CustomGridRow = styled.div.attrs<{
}>((props) => ({
className: `euiDataGridRow ${props.className ?? ''}`,
role: 'row',
}))`
width: fit-content;
}))<{
maxWidth?: number;
}>`
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};
Expand All @@ -99,6 +214,13 @@ const CustomGridRow = styled.div.attrs<{
background-color: ${(props) => (props.theme as EuiTheme).eui.euiColorEmptyShade};
}

[data-gridcell-column-id="timeline-event-detail-row"] > .euiDataGridRowCell__content {
max-width: ${(props) => props.maxWidth}px;
overflow-x: auto;
scrollbar-width: thin;
scroll-padding: 0 0 0 0,
}

&:has(.unifiedDataTable__cell--expanded) {
.euiDataGridRowCell--firstColumn,
.euiDataGridRowCell--lastColumn,
Expand Down Expand Up @@ -130,6 +252,8 @@ const CustomGridRowCellWrapper = styled.div.attrs<{
type CustomTimelineDataGridSingleRowProps = {
rowData: DataTableRecord & TimelineItem;
rowIndex: number;
setRowHeight: (index: number, height: number) => void;
maxWidth: number | undefined;
} & Pick<
CustomTimelineDataGridBodyProps,
'visibleColumns' | 'Cell' | 'enabledRowRenderers' | 'refetch' | 'rowHeight'
Expand Down Expand Up @@ -160,12 +284,23 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
visibleColumns,
Cell,
rowHeight: rowHeightMultiple = 0,
setRowHeight,
maxWidth,
} = 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
Expand All @@ -183,15 +318,17 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
);
const eventTypeRowClassName = useMemo(() => getEventTypeRowClassName(rowData.ecs), [rowData.ecs]);

const [rowHeight, _] = useState<number>(0);

return (
<CustomGridRow
className={`${rowIndex % 2 !== 0 ? 'euiDataGridRow--striped' : ''}`}
$cssRowHeight={cssRowHeight}
key={rowIndex}
ref={rowRef}
maxWidth={maxWidth}
>
<CustomGridRowCellWrapper className={eventTypeRowClassName} $cssRowHeight={cssRowHeight}>
<CustomGridRowCellWrapper className={eventTypeRowClassName} $cssRowHeight={'auto'}>
{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
Expand All @@ -209,6 +346,11 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
{/* Timeline Expanded Row */}
{canShowRowRenderer ? (
<Cell
rowHeightsOptions={{
defaultHeight: 'auto',
}}
/* @ts-expect-error because currently CellProps do not allow string width but it is important to be passed for height calculations */
width={'100%'}
logeekal marked this conversation as resolved.
Show resolved Hide resolved
colIndex={visibleColumns.length - 1} // If the row is being shown, it should always be the last index
visibleRowIndex={rowIndex}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ 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' },

Expand Down Expand Up @@ -331,6 +331,10 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
visibleRowData,
visibleColumns,
setCustomGridBodyProps,
headerRow,
footerRow,
gridWidth,
calculatedTableWidth,
}: EuiDataGridCustomBodyProps) => (
<CustomTimelineDataGridBody
rows={tableRows}
Expand All @@ -341,6 +345,9 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
enabledRowRenderers={enabledRowRenderers}
rowHeight={rowHeight}
refetch={refetch}
headerRow={headerRow}
footerRow={footerRow}
gridWidth={gridWidth}
/>
),
[tableRows, enabledRowRenderers, rowHeight, refetch]
Expand Down Expand Up @@ -397,7 +404,7 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
services={dataGridServices}
visibleCellActions={3}
externalCustomRenderers={customColumnRenderers}
externalCustomRenderers={true ? undefined : customColumnRenderers}
renderDocumentView={EmptyComponent}
rowsPerPageOptions={itemsPerPageOptions}
showFullScreenButton={false}
Expand Down
Loading