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 e179c0298746..3c277d1d4019 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 @@ -5,7 +5,6 @@ * 2.0. */ -import { EuiLoadingContent, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; @@ -369,11 +368,7 @@ export const AlertsTableComponent: React.FC = ({ }, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]); if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { - return ( - - - - ); + return null; } return ( diff --git a/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg b/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg new file mode 100644 index 000000000000..b9a0df1630b2 --- /dev/null +++ b/x-pack/plugins/timelines/public/assets/illustration_product_no_results_magnifying_glass.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 c3c83f6be72c..cdfca4e09eb1 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,19 +8,12 @@ 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, - EuiLoadingContent, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; -import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { Direction, EntityType } from '../../../../common/search_strategy'; import type { DocValueFields } from '../../../../common/search_strategy'; @@ -53,6 +46,7 @@ import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } import { Sort } from '../body/sort'; import { InspectButton, InspectButtonContainer } from '../../inspect'; import { SummaryViewSelector, ViewSelection } from '../event_rendered_view/selector'; +import { TGridLoading, TGridEmpty } from '../shared'; const AlertConsumers: typeof AlertConsumersTyped = AlertConsumersNonTyped; @@ -269,6 +263,8 @@ const TGridIntegratedComponent: React.FC = ({ [deletedEventIds.length, totalCount] ); + const hasAlerts = totalCountMinusDeleted > 0; + const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [ deletedEventIds, events, @@ -300,7 +296,7 @@ const TGridIntegratedComponent: React.FC = ({ data-test-subj="events-viewer-panel" $isFullScreen={globalFullScreen} > - {isFirstUpdate.current && } + {isFirstUpdate.current && } {graphOverlay} @@ -325,61 +321,43 @@ const TGridIntegratedComponent: React.FC = ({ {!graphEventId && graphOverlay == null && ( - - - {totalCountMinusDeleted === 0 && loading === false && ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - )} - {totalCountMinusDeleted > 0 && ( - - )} -
-
+ <> + {!hasAlerts && !loading && } + {hasAlerts && ( + + + + + + )} + )} )} diff --git a/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx new file mode 100644 index 000000000000..563e8224058c --- /dev/null +++ b/x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx @@ -0,0 +1,90 @@ +/* + * 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 from 'react'; +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiImage, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import type { CoreStart } from '../../../../../../../src/core/public'; + +const heights = { + tall: 490, + short: 250, +}; + +export const TGridLoading: React.FC<{ height?: keyof typeof heights }> = ({ height = 'tall' }) => { + return ( + + + + + + + + ); +}; + +const panelStyle = { + maxWidth: 500, +}; + +export const TGridEmpty: React.FC<{ height?: keyof typeof heights }> = ({ height = 'tall' }) => { + const { http } = useKibana().services; + + return ( + + + + + + + + +

+ +

+
+

+ +

+
+
+ + + +
+
+
+
+
+ ); +}; diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index ee9b7be48df6..74dd8c01295b 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexItem } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useState, useRef } from 'react'; import styled from 'styled-components'; @@ -39,10 +38,16 @@ import type { State } from '../../../store/t_grid'; import { useTimelineEvents } from '../../../container'; import { StatefulBody } from '../body'; import { LastUpdatedAt } from '../..'; -import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexItem, UpdatedFlexGroup } from '../styles'; +import { + SELECTOR_TIMELINE_GLOBAL_CONTAINER, + UpdatedFlexItem, + UpdatedFlexGroup, + FullWidthFlexGroup, +} from '../styles'; import { InspectButton, InspectButtonContainer } from '../../inspect'; import { useFetchIndex } from '../../../container/source'; import { AddToCaseAction } from '../../actions/timeline/cases/add_to_case_action'; +import { TGridLoading, TGridEmpty } from '../shared'; export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px const STANDALONE_ID = 'standalone-t-grid'; @@ -68,12 +73,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; `; @@ -255,6 +254,8 @@ const TGridStandaloneComponent: React.FC = ({ () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), [deletedEventIds.length, totalCount] ); + const hasAlerts = totalCountMinusDeleted > 0; + const activeCaseFlowId = useSelector((state: State) => tGridSelectors.activeCaseFlowId(state)); const selectedEvent = useMemo(() => { const matchedEvent = events.find((event) => event.ecs._id === activeCaseFlowId); @@ -338,14 +339,14 @@ const TGridStandaloneComponent: React.FC = ({ return ( - {isFirstUpdate.current && } + {isFirstUpdate.current && } {canQueryTimeline ? ( <> - + @@ -354,28 +355,9 @@ const TGridStandaloneComponent: React.FC = ({ - {totalCountMinusDeleted === 0 && loading === false && ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - )} - {totalCountMinusDeleted > 0 && ( + {!hasAlerts && !loading && } + + {hasAlerts && ( ( }) )<{ $isVisible: boolean }>``; +export const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible?: boolean }>` + overflow: hidden; + margin: 0; + min-height: 490px; + display: ${({ $visible = true }) => ($visible ? 'flex' : 'none')}; +`; + export const UpdatedFlexGroup = styled(EuiFlexGroup)` position: absolute; z-index: ${({ theme }) => theme.eui.euiZLevel1}; diff --git a/x-pack/plugins/timelines/public/methods/index.tsx b/x-pack/plugins/timelines/public/methods/index.tsx index 91802c4eb10e..06bb1ae44321 100644 --- a/x-pack/plugins/timelines/public/methods/index.tsx +++ b/x-pack/plugins/timelines/public/methods/index.tsx @@ -6,7 +6,7 @@ */ import React, { lazy, Suspense } from 'react'; -import { EuiLoadingContent, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; import type { Store } from 'redux'; import { Provider } from 'react-redux'; @@ -17,6 +17,7 @@ import type { LastUpdatedAtProps, LoadingPanelProps, FieldBrowserProps } from '. import type { AddToCaseActionProps } from '../components/actions/timeline/cases/add_to_case_action'; import { initialTGridState } from '../store/t_grid/reducer'; import { createStore } from '../store/t_grid'; +import { TGridLoading } from '../components/t_grid/shared'; const initializeStore = ({ store, @@ -51,13 +52,7 @@ export const getTGridLazy = ( ) => { initializeStore({ store, storage, setStore }); return ( - - - - } - > + }> ); diff --git a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx index adc10ae0a416..a37c00144504 100644 --- a/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx +++ b/x-pack/test/plugin_functional/plugins/timelines_test/public/applications/timelines_test/index.tsx @@ -11,6 +11,7 @@ import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from 'kibana/public'; import { I18nProvider } from '@kbn/i18n/react'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { TimelinesUIStart } from '../../../../../../../plugins/timelines/public'; import { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public'; @@ -60,39 +61,41 @@ const AppRoot = React.memo( - {(timelinesPluginSetup && - timelinesPluginSetup.getTGrid && - timelinesPluginSetup.getTGrid<'standalone'>({ - appId: 'securitySolution', - type: 'standalone', - casePermissions: { - read: true, - crud: true, - }, - columns: [], - indexNames: [], - deletedEventIds: [], - end: '', - footerText: 'Events', - filters: [], - hasAlertsCrudPermissions, - itemsPerPageOptions: [1, 2, 3], - loadingText: 'Loading events', - renderCellValue: () =>
test
, - sort: [], - leadingControlColumns: [], - trailingControlColumns: [], - query: { - query: '', - language: 'kuery', - }, - setRefetch, - start: '', - rowRenderers: [], - filterStatus: 'open', - unit: (n: number) => `${n}`, - })) ?? - null} + + {(timelinesPluginSetup && + timelinesPluginSetup.getTGrid && + timelinesPluginSetup.getTGrid<'standalone'>({ + appId: 'securitySolution', + type: 'standalone', + casePermissions: { + read: true, + crud: true, + }, + columns: [], + indexNames: [], + deletedEventIds: [], + end: '', + footerText: 'Events', + filters: [], + hasAlertsCrudPermissions, + itemsPerPageOptions: [1, 2, 3], + loadingText: 'Loading events', + renderCellValue: () =>
test
, + sort: [], + leadingControlColumns: [], + trailingControlColumns: [], + query: { + query: '', + language: 'kuery', + }, + setRefetch, + start: '', + rowRenderers: [], + filterStatus: 'open', + unit: (n: number) => `${n}`, + })) ?? + null} +