From d66f5c24a9e5a5d41c042553e28bfe6834d8b4c9 Mon Sep 17 00:00:00 2001 From: Pablo Neves Machado Date: Thu, 19 Aug 2021 17:22:39 +0200 Subject: [PATCH 1/8] Update T-Grid to use DataGrid pagination * It also improves the Gtid loading state * DataGrid pagination makes sure that we display the grid with the proper height. --- .../lib/cell_actions/default_cell_actions.tsx | 75 ++++++++- .../components/alerts_table/index.tsx | 4 +- .../common/types/timeline/columns/index.tsx | 2 + .../components/t_grid/body/index.test.tsx | 4 +- .../public/components/t_grid/body/index.tsx | 107 +++++++++--- .../t_grid/body/row_action/index.tsx | 24 ++- .../t_grid/event_rendered_view/index.tsx | 17 +- .../components/t_grid/integrated/index.tsx | 157 +++++++----------- .../components/t_grid/standalone/index.tsx | 8 +- .../timelines/public/container/index.tsx | 2 +- .../timelines/public/methods/index.tsx | 10 +- 11 files changed, 250 insertions(+), 160 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx index 623957ab8b66c..33221b980e5db 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx @@ -34,13 +34,36 @@ const useKibanaServices = () => { return { timelines, filterManager }; }; +/** + * rowIndex is bigger than `data.length` for pages with page numbers bigger than one. + * For that reason, we must calculate `rowIndex % itemsPerPage`. + * + * Ex: + * Given `rowIndex` is `13` and `itemsPerPage` is `10`. + * It means that the `activePage` is `2` and the `pageRowIndex` is `3` + * + * **Warning**: + * Be careful with array out of bounds. `pageRowIndex` can be bigger or equal to `data.length` + * in the scenario where the user changes the event status (Open, Acknowledged, Closed). + */ +export const getPageRowIndex = (rowIndex: number, itemsPerPage: number) => rowIndex % itemsPerPage; + /** the default actions shown in `EuiDataGrid` cells */ export const defaultCellActions: TGridCellAction[] = [ - ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ + rowIndex, + columnId, + Component, + }) => { const { timelines, filterManager } = useKibanaServices(); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + const value = getMappedNonEcsValue({ - data: data[rowIndex], + data: data[pageRowIndex], fieldName: columnId, }); @@ -58,11 +81,20 @@ export const defaultCellActions: TGridCellAction[] = [ ); }, - ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ + rowIndex, + columnId, + Component, + }) => { const { timelines, filterManager } = useKibanaServices(); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + const value = getMappedNonEcsValue({ - data: data[rowIndex], + data: data[pageRowIndex], fieldName: columnId, }); @@ -80,11 +112,20 @@ export const defaultCellActions: TGridCellAction[] = [ ); }, - ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ + rowIndex, + columnId, + Component, + }) => { const { timelines } = useKibanaServices(); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + const value = getMappedNonEcsValue({ - data: data[rowIndex], + data: data[pageRowIndex], fieldName: columnId, }); @@ -122,16 +163,23 @@ export const defaultCellActions: TGridCellAction[] = [ browserFields, data, timelineId, + pageSize, }: { browserFields: BrowserFields; data: TimelineNonEcsData[][]; timelineId: string; + pageSize: number; }) => ({ rowIndex, columnId, Component }) => { const [showTopN, setShowTopN] = useState(false); const onClick = useCallback(() => setShowTopN(!showTopN), [showTopN]); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + const value = getMappedNonEcsValue({ - data: data[rowIndex], + data: data[pageRowIndex], fieldName: columnId, }); @@ -159,11 +207,20 @@ export const defaultCellActions: TGridCellAction[] = [ ); }, - ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ + rowIndex, + columnId, + Component, + }) => { const { timelines } = useKibanaServices(); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + const value = getMappedNonEcsValue({ - data: data[rowIndex], + data: data[pageRowIndex], fieldName: columnId, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 3c0bb0e38b153..4b3c792319cd1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -14,7 +14,6 @@ import { esQuery, Filter } from '../../../../../../../src/plugins/data/public'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RowRendererId, TimelineIdLiteral } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; -import { HeaderSection } from '../../../common/components/header_section'; import { displayErrorToast, displaySuccessToast, @@ -371,8 +370,7 @@ export const AlertsTableComponent: React.FC = ({ if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return ( - - + ); diff --git a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx index 5ca3661eb3afe..f799febf646b8 100644 --- a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx +++ b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx @@ -46,11 +46,13 @@ export type TGridCellAction = ({ browserFields, data, timelineId, + pageSize, }: { browserFields: BrowserFields; /** each row of data is represented as one TimelineNonEcsData[] */ data: TimelineNonEcsData[][]; timelineId: string; + pageSize: number; }) => (props: EuiDataGridColumnCellActionProps) => ReactNode; /** The specification of a column header */ diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx index 2ab5a86fa7ddd..712184cafdd28 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx @@ -66,10 +66,11 @@ describe('Body', () => { excludedRowRendererIds: [], id: 'timeline-test', isSelectAllChecked: false, + isLoading: false, itemsPerPageOptions: [], loadingEventIds: [], loadPage: jest.fn(), - querySize: 25, + pageSize: 25, renderCellValue: TestCellRenderer, rowRenderers: [], selectedEventIds: {}, @@ -78,7 +79,6 @@ describe('Body', () => { showCheckboxes: false, tabType: TimelineTabs.query, tableView: 'gridView', - totalPages: 1, totalItems: 1, leadingControlColumns: [], trailingControlColumns: [], diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 001e405fc10e0..3e6a09edd2bd8 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -15,6 +15,7 @@ import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, + EuiProgress, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; import memoizeOne from 'memoize-one'; @@ -28,7 +29,6 @@ import React, { useState, useContext, } from 'react'; - import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { ThemeContext } from 'styled-components'; @@ -92,14 +92,13 @@ interface OwnProps { leadingControlColumns?: ControlColumnProps[]; loadPage: (newActivePage: number) => void; onRuleChange?: () => void; - querySize: number; + pageSize: number; refetch: Refetch; renderCellValue: (props: CellValueElementProps) => React.ReactNode; rowRenderers: RowRenderer[]; tableView: ViewSelection; tabType: TimelineTabs; totalItems: number; - totalPages: number; trailingControlColumns?: ControlColumnProps[]; unit?: (total: number) => React.ReactNode; hasAlertsCrud?: boolean; @@ -123,6 +122,21 @@ const EmptyHeaderCellRender: ComponentType = () => null; const gridStyle: EuiDataGridStyle = { border: 'none', fontSize: 's', header: 'underline' }; +/** + * rowIndex is bigger than `data.length` for pages with page numbers bigger than one. + * For that reason, we must calculate `rowIndex % itemsPerPage`. + * + * Ex: + * Given `rowIndex` is `13` and `itemsPerPage` is `10`. + * It means that the `activePage` is `2` and the `pageRowIndex` is `3` + * + * **Warning**: + * Be careful with array out of bounds. `pageRowIndex` can be bigger or equal to `data.length` + * in the scenario where the user changes the event status (Open, Acknowledged, Closed). + */ + +export const getPageRowIndex = (rowIndex: number, itemsPerPage: number) => rowIndex % itemsPerPage; + const transformControlColumns = ({ actionColumnsWidth, columnHeaders, @@ -139,6 +153,7 @@ const transformControlColumns = ({ isSelectAllChecked, onSelectPage, browserFields, + pageSize, sort, theme, setEventsLoading, @@ -159,6 +174,7 @@ const transformControlColumns = ({ isSelectAllChecked: boolean; browserFields: BrowserFields; onSelectPage: OnSelectAll; + pageSize: number; sort: SortColumnTimeline[]; theme: EuiTheme; setEventsLoading: SetEventsLoading; @@ -199,7 +215,15 @@ const transformControlColumns = ({ rowIndex, setCellProps, }: EuiDataGridCellValueElementProps) => { - addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps); + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + const rowData = data[pageRowIndex]; + + if (rowData) { + addBuildingBlockStyle(data[pageRowIndex].ecs, theme, setCellProps); + } else { + // disable the cell when it has no data + setCellProps({ style: { display: 'none' } }); + } return ( ( columnHeaders, data, defaultCellActions, - excludedRowRendererIds, filterQuery, filterStatus, id, @@ -258,9 +282,10 @@ export const BodyComponent = React.memo( itemsPerPageOptions, leadingControlColumns = EMPTY_CONTROL_COLUMNS, loadingEventIds, + isLoading, loadPage, onRuleChange, - querySize, + pageSize, refetch, renderCellValue, rowRenderers, @@ -271,7 +296,6 @@ export const BodyComponent = React.memo( tableView = 'gridView', tabType, totalItems, - totalPages, trailingControlColumns = EMPTY_CONTROL_COLUMNS, unit = defaultUnit, hasAlertsCrud, @@ -393,6 +417,7 @@ export const BodyComponent = React.memo( () => ({ additionalControls: ( <> + {isLoading && } {alertCountText} {showBulkActions ? ( <> @@ -451,6 +476,7 @@ export const BodyComponent = React.memo( onAlertStatusActionSuccess, onAlertStatusActionFailure, refetch, + isLoading, ] ); @@ -500,8 +526,8 @@ export const BodyComponent = React.memo( }, [columnHeaders]); const setEventsLoading = useCallback( - ({ eventIds, isLoading }) => { - dispatch(tGridActions.setEventsLoading({ id, eventIds, isLoading })); + ({ eventIds, isLoading: loading }) => { + dispatch(tGridActions.setEventsLoading({ id, eventIds, isLoading: loading })); }, [dispatch, id] ); @@ -544,6 +570,7 @@ export const BodyComponent = React.memo( theme, setEventsLoading, setEventsDeleted, + pageSize, }) ); }, [ @@ -564,6 +591,7 @@ export const BodyComponent = React.memo( onSelectPage, sort, theme, + pageSize, setEventsLoading, setEventsDeleted, ]); @@ -576,6 +604,7 @@ export const BodyComponent = React.memo( data: data.map((row) => row.data), browserFields, timelineId: id, + pageSize, }); return { @@ -584,7 +613,7 @@ export const BodyComponent = React.memo( header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction), }; }), - [browserFields, columnHeaders, data, defaultCellActions, id] + [browserFields, columnHeaders, data, defaultCellActions, id, pageSize] ); const renderTGridCellValue = useMemo(() => { @@ -593,9 +622,13 @@ export const BodyComponent = React.memo( rowIndex, setCellProps, }): React.ReactElement | null => { - const rowData = rowIndex < data.length ? data[rowIndex].data : null; + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + + const rowData = pageRowIndex < data.length ? data[pageRowIndex].data : null; const header = columnHeaders.find((h) => h.id === columnId); - const eventId = rowIndex < data.length ? data[rowIndex]._id : null; + const eventId = pageRowIndex < data.length ? data[pageRowIndex]._id : null; + const ecs = pageRowIndex < data.length ? data[pageRowIndex].ecs : null; + const defaultStyles = useMemo( () => ({ overflow: 'hidden', @@ -605,10 +638,15 @@ export const BodyComponent = React.memo( setCellProps({ style: { ...defaultStyles } }); useEffect(() => { - addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps, defaultStyles); - }, [rowIndex, setCellProps, defaultStyles]); + if (ecs && rowData) { + addBuildingBlockStyle(ecs, theme, setCellProps, defaultStyles); + } else { + // disable the cell when it has no data + setCellProps({ style: { display: 'none' } }); + } + }, [rowIndex, setCellProps, defaultStyles, ecs, rowData]); - if (rowData == null || header == null || eventId == null) { + if (rowData == null || header == null || eventId == null || ecs === null) { return null; } @@ -621,17 +659,35 @@ export const BodyComponent = React.memo( isExpandable: true, isExpanded: false, isDetails: false, - linkValues: getOr([], header.linkField ?? '', data[rowIndex].ecs), + linkValues: getOr([], header.linkField ?? '', ecs), rowIndex, setCellProps, timelineId: tabType != null ? `${id}-${tabType}` : id, - ecsData: data[rowIndex].ecs, + ecsData: ecs, browserFields, rowRenderers, }) as React.ReactElement; }; return Cell; - }, [columnHeaders, data, id, renderCellValue, tabType, theme, browserFields, rowRenderers]); + }, [ + columnHeaders, + data, + id, + renderCellValue, + tabType, + theme, + browserFields, + rowRenderers, + pageSize, + ]); + + const onChangeItemsPerPage = useCallback( + (itemsChangedPerPage) => { + dispatch(tGridActions.updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage })); + }, + + [id, dispatch] + ); return ( <> @@ -645,10 +701,16 @@ export const BodyComponent = React.memo( leadingControlColumns={leadingTGridControlColumns} trailingControlColumns={trailingTGridControlColumns} toolbarVisibility={toolbarVisibility} - rowCount={data.length} + rowCount={totalItems} renderCellValue={renderTGridCellValue} - inMemory={{ level: 'sorting' }} sorting={{ columns: sortingColumns, onSort }} + pagination={{ + pageIndex: activePage, + pageSize, + pageSizeOptions: itemsPerPageOptions, + onChangeItemsPerPage, + onChangePage: loadPage, + }} /> )} {tableView === 'eventRenderedView' && ( @@ -658,8 +720,9 @@ export const BodyComponent = React.memo( events={data} leadingControlColumns={leadingTGridControlColumns ?? []} onChangePage={loadPage} + onChangeItemsPerPage={onChangeItemsPerPage} pageIndex={activePage} - pageSize={querySize} + pageSize={pageSize} pageSizeOptions={itemsPerPageOptions} rowRenderers={rowRenderers} timelineId={id} @@ -693,6 +756,7 @@ const makeMapStateToProps = () => { selectedEventIds, showCheckboxes, sort, + isLoading, } = timeline; return { @@ -700,6 +764,7 @@ const makeMapStateToProps = () => { excludedRowRendererIds, isSelectAllChecked, loadingEventIds, + isLoading, id, selectedEventIds, showCheckboxes: hasAlertsCrud === true && showCheckboxes, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/row_action/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/row_action/index.tsx index da3152509f5cf..622ffc9763e0f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/row_action/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/row_action/index.tsx @@ -38,6 +38,7 @@ type Props = EuiDataGridCellValueElementProps & { width: number; setEventsLoading: SetEventsLoading; setEventsDeleted: SetEventsDeleted; + pageRowIndex: number; }; const RowActionComponent = ({ @@ -49,6 +50,7 @@ const RowActionComponent = ({ loadingEventIds, onRowSelected, onRuleChange, + pageRowIndex, rowIndex, selectedEventIds, showCheckboxes, @@ -58,15 +60,21 @@ const RowActionComponent = ({ setEventsDeleted, width, }: Props) => { - const { data: timelineNonEcsData, ecs: ecsData, _id: eventId, _index: indexName } = useMemo( - () => data[rowIndex], - [data, rowIndex] - ); + const { + data: timelineNonEcsData, + ecs: ecsData, + _id: eventId, + _index: indexName, + } = useMemo(() => { + const rowData: Partial = data[pageRowIndex]; + return rowData ?? {}; + }, [data, pageRowIndex]); const dispatch = useDispatch(); const columnValues = useMemo( () => + timelineNonEcsData && columnHeaders .map( (header) => @@ -83,7 +91,7 @@ const RowActionComponent = ({ const updatedExpandedDetail: TimelineExpandedDetailType = { panelView: 'eventDetail', params: { - eventId, + eventId: eventId ?? '', indexName: indexName ?? '', }, }; @@ -99,7 +107,7 @@ const RowActionComponent = ({ const Action = controlColumn.rowCellRender; - if (data.length === 0 || rowIndex >= data.length) { + if (!timelineNonEcsData || !ecsData || !eventId) { return ; } @@ -107,10 +115,10 @@ const RowActionComponent = ({ <> {Action && ( void; + onChangeItemsPerPage: (newItemsPerPage: number) => void; pageIndex: number; pageSize: number; pageSizeOptions: number[]; @@ -89,6 +85,7 @@ const EventRenderedViewComponent = ({ events, leadingControlColumns, onChangePage, + onChangeItemsPerPage, pageIndex, pageSize, pageSizeOptions, @@ -96,8 +93,6 @@ const EventRenderedViewComponent = ({ timelineId, totalItemCount, }: EventRenderedViewProps) => { - const dispatch = useDispatch(); - const ActionTitle = useMemo( () => ( @@ -220,12 +215,10 @@ const EventRenderedViewComponent = ({ onChangePage(pageChange.page.index); } if (pageChange.page.size !== pageSize) { - dispatch( - tGridActions.updateItemsPerPage({ id: timelineId, itemsPerPage: pageChange.page.size }) - ); + onChangeItemsPerPage(pageChange.page.size); } }, - [dispatch, onChangePage, pageIndex, pageSize, timelineId] + [onChangePage, pageIndex, pageSize, onChangeItemsPerPage] ); const pagination = useMemo( diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index cc34b32b048ac..8353a0068d214 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -8,9 +8,9 @@ import type { AlertConsumers as AlertConsumersTyped } from '@kbn/rule-data-utils'; // @ts-expect-error import { AlertConsumers as AlertConsumersNonTyped } from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac'; -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiProgress } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLoadingContent, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; @@ -39,16 +39,10 @@ import { } from '../../../../../../../src/plugins/data/public'; import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { defaultHeaders } from '../body/column_headers/default_headers'; -import { - calculateTotalPages, - buildCombinedQuery, - getCombinedFilterQuery, - resolverIsShowing, -} from '../helpers'; +import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers'; import { tGridActions, tGridSelectors } from '../../../store/t_grid'; import { useTimelineEvents } from '../../../container'; import { StatefulBody } from '../body'; -import { Footer, footerHeight } from '../footer'; import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } from '../styles'; import { Sort } from '../body/sort'; import { InspectButton, InspectButtonContainer } from '../../inspect'; @@ -86,16 +80,6 @@ const EventsContainerLoading = styled.div.attrs(({ className = '' }) => ({ flex-direction: column; `; -const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>` - overflow: hidden; - margin: 0; - display: ${({ $visible }) => ($visible ? 'flex' : 'none')}; -`; - -const ScrollableFlexItem = styled(EuiFlexItem)` - overflow: auto; -`; - const SECURITY_ALERTS_CONSUMERS = [AlertConsumers.SIEM]; export interface TGridIntegratedProps { @@ -157,7 +141,6 @@ const TGridIntegratedComponent: React.FC = ({ id, indexNames, indexPattern, - isLive, isLoadingIndexPattern, itemsPerPage, itemsPerPageOptions, @@ -176,7 +159,7 @@ const TGridIntegratedComponent: React.FC = ({ const dispatch = useDispatch(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const { uiSettings } = useKibana().services; - const [isQueryLoading, setIsQueryLoading] = useState(false); + const [isQueryLoading, setIsQueryLoading] = useState(true); const [tableView, setTableView] = useState('gridView'); const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []); @@ -279,6 +262,13 @@ const TGridIntegratedComponent: React.FC = ({ const alignItems = tableView === 'gridView' ? 'baseline' : 'center'; + const isFirstUpdate = useRef(true); + useEffect(() => { + if (isFirstUpdate.current && !loading) { + isFirstUpdate.current = false; + } + }, [loading]); + return ( = ({ data-test-subj="events-viewer-panel" $isFullScreen={globalFullScreen} > - {loading && } + {isFirstUpdate.current && } {graphOverlay} {canQueryTimeline ? ( @@ -311,81 +301,56 @@ const TGridIntegratedComponent: React.FC = ({ )} - - - {nonDeletedEvents.length === 0 && loading === false ? ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - ) : ( - <> - + + + } + titleSize="s" + body={ +

+ - {tableView === 'gridView' && ( -