diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
index 843d99cf06cab..4f5ff956f35a1 100644
--- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
@@ -154,7 +154,7 @@ describe.skip('Events Viewer', () => {
});
});
- context('Events columns', () => {
+ context.skip('Events columns', () => {
before(() => {
loginAndWaitForPage(HOSTS_URL);
openEvents();
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts
index 9a5c3e50aa82f..97ac36433ec1f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts
@@ -24,7 +24,7 @@ import { createNewTimeline } from '../tasks/timeline';
import { HOSTS_URL } from '../urls/navigation';
-describe('timeline data providers', () => {
+describe.skip('timeline data providers', () => {
before(() => {
loginAndWaitForPage(HOSTS_URL);
waitForAllHostsToBeLoaded();
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index 049953e21febd..833688ae57993 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -15,11 +15,17 @@ import { wait as waitFor } from '@testing-library/react';
import { mockEventViewerResponse } from './mock';
import { StatefulEventsViewer } from '.';
+import { EventsViewer } from './events_viewer';
import { defaultHeaders } from './default_headers';
import { useFetchIndexPatterns } from '../../../detections/containers/detection_engine/rules/fetch_index_patterns';
import { mockBrowserFields, mockDocValueFields } from '../../containers/source/mock';
import { eventsDefaultModel } from './default_model';
import { useMountAppended } from '../../utils/use_mount_appended';
+import { inputsModel } from '../../store/inputs';
+import { TimelineId } from '../../../../common/types/timeline';
+import { KqlMode } from '../../../timelines/store/timeline/model';
+import { SortDirection } from '../../../timelines/components/timeline/body/sort';
+import { AlertsTableFilterGroup } from '../../../detections/components/alerts_table/alerts_filter_group';
jest.mock('../../components/url_state/normalize_time_range.ts');
@@ -40,6 +46,39 @@ const defaultMocks = {
isLoading: false,
};
+const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
+
+);
+
+const eventsViewerDefaultProps = {
+ browserFields: {},
+ columns: [],
+ dataProviders: [],
+ deletedEventIds: [],
+ docValueFields: [],
+ end: to,
+ filters: [],
+ id: TimelineId.detectionsPage,
+ indexPattern: mockIndexPattern,
+ isLive: false,
+ isLoadingIndexPattern: false,
+ itemsPerPage: 10,
+ itemsPerPageOptions: [],
+ kqlMode: 'filter' as KqlMode,
+ onChangeItemsPerPage: jest.fn(),
+ query: {
+ query: '',
+ language: 'kql',
+ },
+ start: from,
+ sort: {
+ columnId: 'foo',
+ sortDirection: 'none' as SortDirection,
+ },
+ toggleColumn: jest.fn(),
+ utilityBar,
+};
+
describe('EventsViewer', () => {
const mount = useMountAppended();
@@ -213,4 +252,212 @@ describe('EventsViewer', () => {
});
});
});
+
+ describe('headerFilterGroup', () => {
+ test('it renders the provided headerFilterGroup', async () => {
+ const wrapper = mount(
+
+
+ }
+ />
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
+ });
+ });
+
+ test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ const wrapper = mount(
+
+
+ }
+ />
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).not.toHaveStyleRule('visibility', 'hidden');
+ });
+ });
+
+ test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ const wrapper = mount(
+
+
+ }
+ />
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).not.toHaveStyleRule('visibility', 'hidden');
+ });
+ });
+
+ test('it does NOT have a visible HeaderFilterGroupWrapper when Resolver is showing, because graphEventId is a valid id', async () => {
+ const wrapper = mount(
+
+
+ }
+ />
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).toHaveStyleRule('visibility', 'hidden');
+ });
+ });
+
+ test('it (still) renders an invisible headerFilterGroup (to maintain state while hidden) when Resolver is showing, because graphEventId is a valid id', async () => {
+ const wrapper = mount(
+
+
+ }
+ />
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('utilityBar', () => {
+ test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
+ });
+ });
+
+ test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
+ });
+ });
+
+ test('it does NOT render the provided utilityBar when Resolver is showing, because graphEventId is a valid id', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('header inspect button', () => {
+ test('it renders the inspect button when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
+ });
+ });
+
+ test('it renders the inspect button when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
+ });
+ });
+
+ test('it does NOT render the inspect button when Resolver is showing, because graphEventId is a valid id', async () => {
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(false);
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
index 3f474da102ca4..bc036b38524ba 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
@@ -22,7 +22,7 @@ import { StatefulBody } from '../../../timelines/components/timeline/body/statef
import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
import { OnChangeItemsPerPage } from '../../../timelines/components/timeline/events';
import { Footer, footerHeight } from '../../../timelines/components/timeline/footer';
-import { combineQueries } from '../../../timelines/components/timeline/helpers';
+import { combineQueries, resolverIsShowing } from '../../../timelines/components/timeline/helpers';
import { TimelineRefetch } from '../../../timelines/components/timeline/refetch_timeline';
import { EventDetailsWidthProvider } from './event_details_width_context';
import * as i18n from './translations';
@@ -73,6 +73,16 @@ const EventsContainerLoading = styled.div`
overflow: auto;
`;
+/**
+ * Hides stateful headerFilterGroup implementations, but prevents the component
+ * from being unmounted, to preserve the state of the component
+ */
+const HeaderFilterGroupWrapper = styled.header<{ show: boolean }>`
+ ${({ show }) => css`
+ ${show ? '' : 'visibility: hidden;'};
+ `}
+`;
+
interface Props {
browserFields: BrowserFields;
columns: ColumnHeaderOptions[];
@@ -234,14 +244,21 @@ const EventsViewerComponent: React.FC = ({
return (
<>
- {headerFilterGroup}
+ {headerFilterGroup && (
+
+ {headerFilterGroup}
+
+ )}
- {utilityBar && (
+ {utilityBar && !resolverIsShowing(graphEventId) && (
{utilityBar?.(refetch, totalCountMinusDeleted)}
)}
@@ -307,6 +324,7 @@ export const EventsViewer = React.memo(
prevProps.deletedEventIds === nextProps.deletedEventIds &&
prevProps.end === nextProps.end &&
deepEqual(prevProps.filters, nextProps.filters) &&
+ prevProps.headerFilterGroup === nextProps.headerFilterGroup &&
prevProps.height === nextProps.height &&
prevProps.id === nextProps.id &&
deepEqual(prevProps.indexPattern, nextProps.indexPattern) &&
diff --git a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx
index 65901ec589daf..b52438486406e 100644
--- a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx
@@ -39,16 +39,27 @@ const Wrapper = styled.aside<{ isSticky?: boolean }>`
`;
Wrapper.displayName = 'Wrapper';
+const FiltersGlobalContainer = styled.header<{ show: boolean }>`
+ ${({ show }) => css`
+ ${show ? '' : 'display: none;'};
+ `}
+`;
+
+FiltersGlobalContainer.displayName = 'FiltersGlobalContainer';
+
export interface FiltersGlobalProps {
children: React.ReactNode;
+ show?: boolean;
}
-export const FiltersGlobal = React.memo(({ children }) => (
+export const FiltersGlobal = React.memo(({ children, show = true }) => (
{({ style, isSticky }) => (
-
- {children}
-
+
+
+ {children}
+
+
)}
));
diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx
index b33ce22651d65..32f6216be63f2 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx
@@ -131,4 +131,28 @@ describe('HeaderSection', () => {
.exists()
).toBe(true);
});
+
+ test('it renders an inspect button when an `id` is provided', () => {
+ const wrapper = mount(
+
+
+ {'Test children'}
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(true);
+ });
+
+ test('it does NOT an inspect button when an `id` is NOT provided', () => {
+ const wrapper = mount(
+
+
+ {'Test children'}
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="inspect-icon-button"]').first().exists()).toBe(false);
+ });
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx
index ba64868b70817..a227d2d3c3a8e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx
@@ -36,7 +36,7 @@ const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged
}, [setFilterGroup, onFilterGroupChanged]);
return (
-
+
{
= ({
filters,
+ graphEventId,
query,
setAbsoluteRangeDatePicker,
}) => {
@@ -151,7 +156,7 @@ export const DetectionEnginePageComponent: React.FC = ({
{indicesExist ? (
-
+
@@ -232,13 +237,19 @@ export const DetectionEnginePageComponent: React.FC = ({
const makeMapStateToProps = () => {
const getGlobalInputs = inputsSelectors.globalSelector();
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
return (state: State) => {
const globalInputs: InputsRange = getGlobalInputs(state);
const { query, filters } = globalInputs;
+ const timeline: TimelineModel =
+ getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults;
+ const { graphEventId } = timeline;
+
return {
query,
filters,
+ graphEventId,
};
};
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
index a251c617e542a..5e6587dab1736 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
@@ -82,6 +82,7 @@ describe('RuleDetailsPageComponent', () => {
{
export const RuleDetailsPageComponent: FC = ({
filters,
+ graphEventId,
query,
setAbsoluteRangeDatePicker,
}) => {
@@ -351,7 +356,7 @@ export const RuleDetailsPageComponent: FC = ({
{indicesExist ? (
-
+
@@ -541,13 +546,19 @@ RuleDetailsPageComponent.displayName = 'RuleDetailsPageComponent';
const makeMapStateToProps = () => {
const getGlobalInputs = inputsSelectors.globalSelector();
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
return (state: State) => {
const globalInputs: InputsRange = getGlobalInputs(state);
const { query, filters } = globalInputs;
+ const timeline: TimelineModel =
+ getTimeline(state, TimelineId.detectionsRulesDetailsPage) ?? timelineDefaults;
+ const { graphEventId } = timeline;
+
return {
query,
filters,
+ graphEventId,
};
};
};
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
index 447d003625c8f..781aa711ff0d9 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
+import { EuiHorizontalRule, EuiSpacer, EuiWindowEvent } from '@elastic/eui';
+import { noop } from 'lodash/fp';
import React, { useEffect, useCallback, useMemo } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { StickyContainer } from 'react-sticky';
@@ -44,6 +45,13 @@ import { navTabsHostDetails } from './nav_tabs';
import { HostDetailsProps } from './types';
import { type } from './utils';
import { getHostDetailsPageFilters } from './helpers';
+import { showGlobalFilters } from '../../../timelines/components/timeline/helpers';
+import { useFullScreen } from '../../../common/containers/use_full_screen';
+import { Display } from '../display';
+import { timelineSelectors } from '../../../timelines/store/timeline';
+import { TimelineModel } from '../../../timelines/store/timeline/model';
+import { TimelineId } from '../../../../common/types/timeline';
+import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
const HostOverviewManage = manageQuery(HostOverview);
const KpiHostDetailsManage = manageQuery(KpiHostsComponent);
@@ -51,6 +59,7 @@ const KpiHostDetailsManage = manageQuery(KpiHostsComponent);
const HostDetailsComponent = React.memo(
({
filters,
+ graphEventId,
query,
setAbsoluteRangeDatePicker,
setHostDetailsTablesActivePageToZero,
@@ -58,6 +67,7 @@ const HostDetailsComponent = React.memo(
hostDetailsPagePath,
}) => {
const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime();
+ const { globalFullScreen } = useFullScreen();
useEffect(() => {
setHostDetailsTablesActivePageToZero();
}, [setHostDetailsTablesActivePageToZero, detailName]);
@@ -93,90 +103,93 @@ const HostDetailsComponent = React.memo(
<>
{indicesExist ? (
-
+
+
-
-
- }
- title={detailName}
- />
-
-
- {({ hostOverview, loading, id, inspect, refetch }) => (
-
- {({ isLoadingAnomaliesData, anomaliesData }) => (
- {
- const fromTo = scoreIntervalToDateTime(score, interval);
- setAbsoluteRangeDatePicker({
- id: 'global',
- from: fromTo.from,
- to: fromTo.to,
- });
- }}
- />
- )}
-
- )}
-
-
-
-
-
- {({ kpiHostDetails, id, inspect, loading, refetch }) => (
-
- )}
-
-
-
-
-
-
-
+
+
+
+ }
+ title={detailName}
+ />
+
+
+ {({ hostOverview, loading, id, inspect, refetch }) => (
+
+ {({ isLoadingAnomaliesData, anomaliesData }) => (
+ {
+ const fromTo = scoreIntervalToDateTime(score, interval);
+ setAbsoluteRangeDatePicker({
+ id: 'global',
+ from: fromTo.from,
+ to: fromTo.to,
+ });
+ }}
+ />
+ )}
+
+ )}
+
+
+
+
+
+ {({ kpiHostDetails, id, inspect, loading, refetch }) => (
+
+ )}
+
+
+
+
+
+
+
+
{
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
- return (state: State) => ({
- query: getGlobalQuerySelector(state),
- filters: getGlobalFiltersQuerySelector(state),
- });
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
+ return (state: State) => {
+ const timeline: TimelineModel =
+ getTimeline(state, TimelineId.hostsPageEvents) ?? timelineDefaults;
+ const { graphEventId } = timeline;
+
+ return {
+ query: getGlobalQuerySelector(state),
+ filters: getGlobalFiltersQuerySelector(state),
+ graphEventId,
+ };
+ };
};
const mapDispatchToProps = {
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
index a3885eac5377c..1219effa5ff6d 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -26,6 +26,7 @@ import { KpiHostsQuery } from '../containers/kpi_hosts';
import { useFullScreen } from '../../common/containers/use_full_screen';
import { useGlobalTime } from '../../common/containers/use_global_time';
import { useWithSource } from '../../common/containers/source';
+import { TimelineId } from '../../../common/types/timeline';
import { LastEventIndexKey } from '../../graphql/types';
import { useKibana } from '../../common/lib/kibana';
import { convertToBuildEsQuery } from '../../common/lib/keury';
@@ -44,11 +45,15 @@ import { HostsComponentProps } from './types';
import { filterHostData } from './navigation';
import { hostsModel } from '../store';
import { HostsTableType } from '../store/model';
+import { showGlobalFilters } from '../../timelines/components/timeline/helpers';
+import { timelineSelectors } from '../../timelines/store/timeline';
+import { timelineDefaults } from '../../timelines/store/timeline/defaults';
+import { TimelineModel } from '../../timelines/store/timeline/model';
const KpiHostsComponentManage = manageQuery(KpiHostsComponent);
export const HostsComponent = React.memo(
- ({ filters, query, setAbsoluteRangeDatePicker, hostsPagePath }) => {
+ ({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => {
const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime();
const { globalFullScreen } = useFullScreen();
const capabilities = useMlCapabilities();
@@ -93,7 +98,7 @@ export const HostsComponent = React.memo(
{indicesExist ? (
-
+
@@ -167,10 +172,22 @@ HostsComponent.displayName = 'HostsComponent';
const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
- const mapStateToProps = (state: State) => ({
- query: getGlobalQuerySelector(state),
- filters: getGlobalFiltersQuerySelector(state),
- });
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
+ const mapStateToProps = (state: State) => {
+ const hostsPageEventsTimeline: TimelineModel =
+ getTimeline(state, TimelineId.hostsPageEvents) ?? timelineDefaults;
+ const { graphEventId: hostsPageEventsGraphEventId } = hostsPageEventsTimeline;
+
+ const hostsPageExternalAlertsTimeline: TimelineModel =
+ getTimeline(state, TimelineId.hostsPageExternalAlerts) ?? timelineDefaults;
+ const { graphEventId: hostsPageExternalAlertsGraphEventId } = hostsPageExternalAlertsTimeline;
+
+ return {
+ query: getGlobalQuerySelector(state),
+ filters: getGlobalFiltersQuerySelector(state),
+ graphEventId: hostsPageEventsGraphEventId ?? hostsPageExternalAlertsGraphEventId,
+ };
+ };
return mapStateToProps;
};
diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx
index 0def110c45a14..ca8da4eb711e5 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx
@@ -41,6 +41,11 @@ import { OverviewEmpty } from '../../overview/components/overview_empty';
import * as i18n from './translations';
import { NetworkComponentProps } from './types';
import { NetworkRouteType } from './navigation/types';
+import { showGlobalFilters } from '../../timelines/components/timeline/helpers';
+import { timelineSelectors } from '../../timelines/store/timeline';
+import { TimelineId } from '../../../common/types/timeline';
+import { timelineDefaults } from '../../timelines/store/timeline/defaults';
+import { TimelineModel } from '../../timelines/store/timeline/model';
const KpiNetworkComponentManage = manageQuery(KpiNetworkComponent);
const sourceId = 'default';
@@ -48,6 +53,7 @@ const sourceId = 'default';
const NetworkComponent = React.memo(
({
filters,
+ graphEventId,
query,
setAbsoluteRangeDatePicker,
networkPagePath,
@@ -100,7 +106,7 @@ const NetworkComponent = React.memo(
{indicesExist ? (
-
+
@@ -189,10 +195,18 @@ NetworkComponent.displayName = 'NetworkComponent';
const makeMapStateToProps = () => {
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
- const mapStateToProps = (state: State) => ({
- query: getGlobalQuerySelector(state),
- filters: getGlobalFiltersQuerySelector(state),
- });
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
+ const mapStateToProps = (state: State) => {
+ const timeline: TimelineModel =
+ getTimeline(state, TimelineId.networkPageExternalAlerts) ?? timelineDefaults;
+ const { graphEventId } = timeline;
+
+ return {
+ query: getGlobalQuerySelector(state),
+ filters: getGlobalFiltersQuerySelector(state),
+ graphEventId,
+ };
+ };
return mapStateToProps;
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
index c371d1862be72..21b96dfe4118d 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
@@ -9,7 +9,7 @@ import { mockIndexPattern } from '../../../common/mock';
import { DataProviderType } from './data_providers/data_provider';
import { mockDataProviders } from './data_providers/mock/mock_data_providers';
-import { buildGlobalQuery, combineQueries } from './helpers';
+import { buildGlobalQuery, combineQueries, resolverIsShowing, showGlobalFilters } from './helpers';
import { mockBrowserFields } from '../../../common/containers/source/mock';
import { EsQueryConfig, Filter, esFilters } from '../../../../../../../src/plugins/data/public';
@@ -500,4 +500,44 @@ describe('Combined Queries', () => {
'{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 3"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 4"}}],"minimum_should_match":1}}]}}]}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 2"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 5"}}],"minimum_should_match":1}}]}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}'
);
});
+
+ describe('resolverIsShowing', () => {
+ test('it returns true when graphEventId is NOT an empty string', () => {
+ expect(resolverIsShowing('a valid id')).toBe(true);
+ });
+
+ test('it returns false when graphEventId is undefined', () => {
+ expect(resolverIsShowing(undefined)).toBe(false);
+ });
+
+ test('it returns false when graphEventId is an empty string', () => {
+ expect(resolverIsShowing('')).toBe(false);
+ });
+ });
+
+ describe('showGlobalFilters', () => {
+ test('it returns false when `globalFullScreen` is true and `graphEventId` is NOT an empty string, because Resolver IS showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: true, graphEventId: 'a valid id' })).toBe(false);
+ });
+
+ test('it returns true when `globalFullScreen` is true and `graphEventId` is undefined, because Resolver is NOT showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: true, graphEventId: undefined })).toBe(true);
+ });
+
+ test('it returns true when `globalFullScreen` is true and `graphEventId` is an empty string, because Resolver is NOT showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: true, graphEventId: '' })).toBe(true);
+ });
+
+ test('it returns true when `globalFullScreen` is false and `graphEventId` is NOT an empty string, because Resolver IS showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: false, graphEventId: 'a valid id' })).toBe(true);
+ });
+
+ test('it returns true when `globalFullScreen` is false and `graphEventId` is undefined, because Resolver is NOT showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: false, graphEventId: undefined })).toBe(true);
+ });
+
+ test('it returns true when `globalFullScreen` is false and `graphEventId` is an empty string, because Resolver is NOT showing', () => {
+ expect(showGlobalFilters({ globalFullScreen: false, graphEventId: '' })).toBe(true);
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
index b21ea3e4f86e9..84387720b5b11 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
@@ -158,3 +158,14 @@ export const combineQueries = ({
export const STATEFUL_EVENT_CSS_CLASS_NAME = 'event-column-view';
export const DEFAULT_ICON_BUTTON_WIDTH = 24;
+
+export const resolverIsShowing = (graphEventId: string | undefined): boolean =>
+ graphEventId != null && graphEventId !== '';
+
+export const showGlobalFilters = ({
+ globalFullScreen,
+ graphEventId,
+}: {
+ globalFullScreen: boolean;
+ graphEventId: string | undefined;
+}): boolean => (globalFullScreen && resolverIsShowing(graphEventId) ? false : true);