in x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx [93:442]
function TimelineDataTableMemo({
columns,
columnIds,
dataView,
activeTab,
timelineId,
itemsPerPage,
itemsPerPageOptions,
rowRenderers,
sort,
events,
isSortEnabled = true,
onFieldEdited,
refetch,
dataLoadingState,
totalCount,
onFetchMoreRecords,
updatedAt,
isTextBasedQuery = false,
onSetColumns,
onSort,
onFilter,
leadingControlColumns,
onUpdatePageIndex,
}) {
const dispatch = useDispatch();
// Store context in state rather than creating object in provider value={} to prevent re-renders caused by a new object being created
const [activeStatefulEventContext] = useState({
timelineID: timelineId,
enableHostDetailsFlyout: true,
enableIpDetailsFlyout: true,
tabType: activeTab,
});
const {
services: {
uiSettings,
fieldFormats,
storage,
dataViewFieldEditor,
notifications: { toasts: toastsService },
telemetry,
theme,
data: dataPluginContract,
},
} = useKibana();
const [expandedDoc, setExpandedDoc] = useState<DataTableRecord & TimelineItem>();
const onCloseExpandableFlyout = useCallback((id: string) => {
setExpandedDoc((prev) => (!prev ? prev : undefined));
}, []);
const { closeFlyout, openFlyout } = useExpandableFlyoutApi();
useOnExpandableFlyoutClose({ callback: onCloseExpandableFlyout });
const showTimeCol = useMemo(() => !!dataView && !!dataView.timeFieldName, [dataView]);
const {
rowHeight,
sampleSize,
excludedRowRendererIds,
columns: timelineColumns,
} = useSelector((state: State) => selectTimelineById(state, timelineId));
const settings: UnifiedDataTableProps['settings'] = useMemo(() => {
const _columns: Record<string, UnifiedDataTableSettingsColumn> = {};
timelineColumns.forEach((timelineColumn) => {
_columns[timelineColumn.id] = {
width: timelineColumn.initialWidth ?? undefined,
};
});
return {
columns: _columns,
};
}, [timelineColumns]);
const { tableRows, tableStylesOverride } = useMemo(
() => transformTimelineItemToUnifiedRows({ events, dataView }),
[events, dataView]
);
const handleOnEventDetailPanelOpened = useCallback(
(eventData: DataTableRecord & TimelineItem) => {
openFlyout({
right: {
id: DocumentDetailsRightPanelKey,
params: {
id: eventData._id,
indexName: eventData.ecs._index ?? '',
scopeId: timelineId,
},
},
});
telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: timelineId,
panel: 'right',
});
},
[openFlyout, timelineId, telemetry]
);
const onSetExpandedDoc = useCallback(
(newDoc?: DataTableRecord) => {
if (newDoc) {
const timelineDoc = tableRows.find((r) => r.id === newDoc.id);
setExpandedDoc(timelineDoc);
if (timelineDoc) {
handleOnEventDetailPanelOpened(timelineDoc);
}
} else {
closeFlyout();
setExpandedDoc(undefined);
}
},
[tableRows, handleOnEventDetailPanelOpened, closeFlyout]
);
const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
(colSettings) => {
if (colSettings.width) {
dispatch(
timelineActions.updateColumnWidth({
columnId: colSettings.columnId,
id: timelineId,
width: Math.round(colSettings.width),
})
);
}
},
[dispatch, timelineId]
);
const onChangeItemsPerPage = useCallback<
NonNullable<UnifiedDataTableProps['onUpdateRowsPerPage']>
>(
(itemsChangedPerPage) => {
dispatch(
timelineActions.updateItemsPerPage({ id: timelineId, itemsPerPage: itemsChangedPerPage })
);
},
[dispatch, timelineId]
);
const customColumnRenderers = useMemo(
() =>
getFormattedFields({
dataTableRows: tableRows,
scopeId: 'timeline',
headers: columns,
}),
[columns, tableRows]
);
const handleFetchMoreRecords = useCallback(() => {
onFetchMoreRecords();
}, [onFetchMoreRecords]);
const additionalControls = useMemo(
() => <ToolbarAdditionalControls timelineId={timelineId} updatedAt={updatedAt} />,
[timelineId, updatedAt]
);
const cellActionsMetadata = useMemo(() => ({ scopeId: timelineId }), [timelineId]);
const onUpdateSampleSize = useCallback(
(newSampleSize: number) => {
if (newSampleSize !== sampleSize) {
dispatch(timelineActions.updateSampleSize({ id: timelineId, sampleSize: newSampleSize }));
}
},
[dispatch, sampleSize, timelineId]
);
const onUpdateRowHeight = useCallback(
(newRowHeight: number) => {
if (newRowHeight !== rowHeight) {
dispatch(timelineActions.updateRowHeight({ id: timelineId, rowHeight: newRowHeight }));
}
},
[dispatch, rowHeight, timelineId]
);
const dataGridServices = useMemo(() => {
return {
theme,
fieldFormats,
storage,
toastNotifications: toastsService,
uiSettings,
dataViewFieldEditor,
data: dataPluginContract,
};
}, [
theme,
fieldFormats,
storage,
toastsService,
uiSettings,
dataViewFieldEditor,
dataPluginContract,
]);
const enabledRowRenderers = useMemo(() => {
if (excludedRowRendererIds && excludedRowRendererIds.length === RowRendererCount) return [];
if (!excludedRowRendererIds) return rowRenderers;
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
*/
const trailingControlColumns: EuiDataGridProps['trailingControlColumns'] = useMemo(
() => [
{
id: TIMELINE_EVENT_DETAIL_ROW_ID,
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' } },
rowCellRender: JEST_ENVIRONMENT
? TimelineEventDetailRowRendererComp
: React.memo(TimelineEventDetailRowRendererComp),
},
],
[TimelineEventDetailRowRendererComp]
);
/**
* Ref: https://eui.elastic.co/#/tabular-content/data-grid-advanced#custom-body-renderer
*/
const renderCustomBodyCallback = useCallback(
({
Cell,
visibleRowData,
visibleColumns,
setCustomGridBodyProps,
gridWidth,
headerRow,
footerRow,
}: EuiDataGridCustomBodyProps) => (
<CustomTimelineDataGridBody
rows={tableRows}
Cell={Cell}
visibleColumns={visibleColumns}
visibleRowData={visibleRowData}
headerRow={headerRow}
footerRow={footerRow}
setCustomGridBodyProps={setCustomGridBodyProps}
enabledRowRenderers={enabledRowRenderers}
rowHeight={rowHeight}
gridWidth={gridWidth}
refetch={refetch}
/>
),
[tableRows, enabledRowRenderers, rowHeight, refetch]
);
const finalRenderCustomBodyCallback = useMemo(() => {
return enabledRowRenderers.length > 0 ? renderCustomBodyCallback : undefined;
}, [enabledRowRenderers.length, renderCustomBodyCallback]);
const finalTrailControlColumns = useMemo(() => {
return enabledRowRenderers.length > 0 ? trailingControlColumns : undefined;
}, [enabledRowRenderers.length, trailingControlColumns]);
return (
<StatefulEventContext.Provider value={activeStatefulEventContext}>
<StyledTimelineUnifiedDataTable>
{(dataLoadingState === DataLoadingState.loading ||
dataLoadingState === DataLoadingState.loadingMore) && (
<StyledEuiProgress data-test-subj="discoverDataGridUpdating" size="xs" color="accent" />
)}
<UnifiedTimelineGlobalStyles />
<DataGridMemoized
ariaLabelledBy="timelineDocumentsAriaLabel"
className="udtTimeline"
columns={columnIds}
expandedDoc={expandedDoc}
gridStyleOverride={tableStylesOverride}
dataView={dataView}
showColumnTokens={true}
loadingState={dataLoadingState}
onFilter={onFilter}
onResize={onResizeDataGrid}
onSetColumns={onSetColumns}
onSort={!isTextBasedQuery ? onSort : undefined}
rows={tableRows}
sampleSizeState={sampleSize || 500}
onUpdateSampleSize={onUpdateSampleSize}
setExpandedDoc={onSetExpandedDoc}
showTimeCol={showTimeCol}
isSortEnabled={isSortEnabled}
sort={sort}
rowHeightState={rowHeight}
isPlainRecord={isTextBasedQuery}
rowsPerPageState={itemsPerPage}
onUpdateRowsPerPage={onChangeItemsPerPage}
onUpdateRowHeight={onUpdateRowHeight}
onFieldEdited={onFieldEdited}
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
services={dataGridServices}
visibleCellActions={3}
externalCustomRenderers={customColumnRenderers}
renderDocumentView={EmptyComponent}
rowsPerPageOptions={itemsPerPageOptions}
showFullScreenButton={false}
maxDocFieldsDisplayed={50}
consumer="timeline"
totalHits={totalCount}
onFetchMoreRecords={handleFetchMoreRecords}
configRowHeight={3}
showMultiFields={true}
cellActionsMetadata={cellActionsMetadata}
externalAdditionalControls={additionalControls}
renderCustomGridBody={finalRenderCustomBodyCallback}
trailingControlColumns={finalTrailControlColumns}
externalControlColumns={leadingControlColumns}
onUpdatePageIndex={onUpdatePageIndex}
getRowIndicator={getTimelineRowTypeIndicator}
settings={settings}
/>
</StyledTimelineUnifiedDataTable>
</StatefulEventContext.Provider>
);
}