diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/embeddables/__snapshots__/embedded_map.test.tsx.snap
index 456e07cf9cd15..4c3cbecc7593d 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/__snapshots__/embedded_map.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/__snapshots__/embedded_map.test.tsx.snap
@@ -1,34 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmbeddedMapComponent renders correctly against snapshot 1`] = `
-
-
-
-
- Map configuration help
-
-
-
- }
- >
-
-
-
-
-
-
+
`;
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx
index ae0d3c2256e07..219409b10be6c 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.test.tsx
@@ -4,36 +4,169 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallow } from 'enzyme';
+import { mount, ReactWrapper, shallow } from 'enzyme';
import React from 'react';
+import * as redux from 'react-redux';
+import { act } from 'react-dom/test-utils';
import '../../../common/mock/match_media';
import { useIndexPatterns } from '../../../common/hooks/use_index_patterns';
+import { TestProviders } from '../../../common/mock';
+
import { EmbeddedMapComponent } from './embedded_map';
+import { createEmbeddable } from './embedded_map_helpers';
const mockUseIndexPatterns = useIndexPatterns as jest.Mock;
jest.mock('../../../common/hooks/use_index_patterns');
mockUseIndexPatterns.mockImplementation(() => [true, []]);
jest.mock('../../../common/lib/kibana');
+jest.mock('./embedded_map_helpers', () => ({
+ createEmbeddable: jest.fn(),
+}));
+jest.mock('../../../common/lib/kibana', () => {
+ return {
+ useKibana: jest.fn().mockReturnValue({
+ services: {
+ embeddable: {
+ EmbeddablePanel: jest.fn(() =>
),
+ },
+ docLinks: { ELASTIC_WEBSITE_URL: 'ELASTIC_WEBSITE_URL' },
+ },
+ }),
+ };
+});
+
+jest.mock('./index_patterns_missing_prompt', () => {
+ return {
+ IndexPatternsMissingPrompt: jest.fn(() => ),
+ };
+});
describe('EmbeddedMapComponent', () => {
- let setQuery: jest.Mock;
+ const setQuery: jest.Mock = jest.fn();
+ const mockSelector = {
+ kibanaIndexPatterns: [
+ { id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9', title: 'filebeat-*' },
+ { id: '28995490-023d-11eb-bcb6-6ba0578012a9', title: 'auditbeat-*' },
+ ],
+ sourcererScope: { selectedPatterns: ['filebeat-*', 'packetbeat-*'] },
+ };
+ const mockCreateEmbeddable = {
+ destroyed: false,
+ enhancements: { dynamicActions: {} },
+ getActionContext: jest.fn(),
+ getFilterActions: jest.fn(),
+ id: '70969ddc-4d01-4048-8073-4ea63d595638',
+ input: {
+ viewMode: 'view',
+ title: 'Source -> Destination Point-to-Point Map',
+ id: '70969ddc-4d01-4048-8073-4ea63d595638',
+ filters: Array(0),
+ hidePanelTitles: true,
+ },
+ input$: {},
+ isContainer: false,
+ output: {},
+ output$: {},
+ parent: undefined,
+ parentSubscription: undefined,
+ renderComplete: {},
+ runtimeId: 1,
+ reload: jest.fn(),
+ setLayerList: jest.fn(),
+ setEventHandlers: jest.fn(),
+ setRenderTooltipContent: jest.fn(),
+ type: 'map',
+ updateInput: jest.fn(),
+ };
+ const testProps = {
+ endDate: '2019-08-28T05:50:57.877Z',
+ filters: [],
+ query: { query: '', language: 'kuery' },
+ setQuery,
+ startDate: '2019-08-28T05:50:47.877Z',
+ };
beforeEach(() => {
- setQuery = jest.fn();
+ setQuery.mockClear();
});
test('renders correctly against snapshot', () => {
const wrapper = shallow(
-
+
+
+
);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find('EmbeddedMapComponent')).toMatchSnapshot();
+ });
+
+ test('renders services.embeddable.EmbeddablePanel', async () => {
+ const spy = jest.spyOn(redux, 'useSelector');
+ spy.mockReturnValue(mockSelector);
+
+ (createEmbeddable as jest.Mock).mockResolvedValue(mockCreateEmbeddable);
+
+ let wrapper: ReactWrapper;
+ await act(async () => {
+ wrapper = mount(
+
+
+
+ );
+ });
+
+ wrapper!.update();
+
+ expect(wrapper!.find('[data-test-subj="EmbeddablePanel"]').exists()).toEqual(true);
+ expect(wrapper!.find('[data-test-subj="IndexPatternsMissingPrompt"]').exists()).toEqual(false);
+ expect(wrapper!.find('[data-test-subj="loading-panel"]').exists()).toEqual(false);
+ });
+
+ test('renders IndexPatternsMissingPrompt', async () => {
+ const spy = jest.spyOn(redux, 'useSelector');
+ spy.mockReturnValue({
+ ...mockSelector,
+ kibanaIndexPatterns: [],
+ });
+
+ (createEmbeddable as jest.Mock).mockResolvedValue(mockCreateEmbeddable);
+
+ let wrapper: ReactWrapper;
+ await act(async () => {
+ wrapper = mount(
+
+
+
+ );
+ });
+
+ wrapper!.update();
+
+ expect(wrapper!.find('[data-test-subj="EmbeddablePanel"]').exists()).toEqual(false);
+ expect(wrapper!.find('[data-test-subj="IndexPatternsMissingPrompt"]').exists()).toEqual(true);
+ expect(wrapper!.find('[data-test-subj="loading-panel"]').exists()).toEqual(false);
+ });
+
+ test('renders Loader', async () => {
+ const spy = jest.spyOn(redux, 'useSelector');
+ spy.mockReturnValue(mockSelector);
+
+ (createEmbeddable as jest.Mock).mockResolvedValue(null);
+
+ let wrapper: ReactWrapper;
+ await act(async () => {
+ wrapper = mount(
+
+
+
+ );
+ });
+
+ wrapper!.update();
+
+ expect(wrapper!.find('[data-test-subj="EmbeddablePanel"]').exists()).toEqual(false);
+ expect(wrapper!.find('[data-test-subj="IndexPatternsMissingPrompt"]').exists()).toEqual(false);
+ expect(wrapper!.find('[data-test-subj="loading-panel"]').exists()).toEqual(true);
});
});
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
index 4d96c213818aa..7ae8aecdab606 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map.tsx
@@ -5,27 +5,31 @@
*/
import { EuiLink, EuiText } from '@elastic/eui';
-import React, { useEffect, useState } from 'react';
+import deepEqual from 'fast-deep-equal';
+import React, { useEffect, useState, useMemo } from 'react';
import { createPortalNode, InPortal } from 'react-reverse-portal';
import styled, { css } from 'styled-components';
-import { ErrorEmbeddable } from '../../../../../../../src/plugins/embeddable/public';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
-import { getIndexPatternTitleIdMapping } from '../../../common/hooks/api/helpers';
-import { useIndexPatterns } from '../../../common/hooks/use_index_patterns';
+import { useSelector } from 'react-redux';
+import {
+ ErrorEmbeddable,
+ isErrorEmbeddable,
+} from '../../../../../../../src/plugins/embeddable/public';
import { Loader } from '../../../common/components/loader';
import { displayErrorToast, useStateToaster } from '../../../common/components/toasters';
import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
import { Embeddable } from './embeddable';
import { EmbeddableHeader } from './embeddable_header';
-import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers';
+import { createEmbeddable } from './embedded_map_helpers';
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
-import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
+import { useKibana } from '../../../common/lib/kibana';
+import { getDefaultSourcererSelector } from './selector';
+import { getLayerList } from './map_config';
interface EmbeddableMapProps {
maintainRatio?: boolean;
@@ -86,13 +90,19 @@ export const EmbeddedMapComponent = ({
const [embeddable, setEmbeddable] = React.useState(
undefined
);
- const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [isIndexError, setIsIndexError] = useState(false);
const [, dispatchToaster] = useStateToaster();
- const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns();
- const [siemDefaultIndices] = useUiSetting$(DEFAULT_INDEX_KEY);
+ const defaultSourcererScopeSelector = useMemo(getDefaultSourcererSelector, []);
+ const { kibanaIndexPatterns, sourcererScope } = useSelector(
+ defaultSourcererScopeSelector,
+ deepEqual
+ );
+
+ const [mapIndexPatterns, setMapIndexPatterns] = useState(
+ kibanaIndexPatterns.filter((kip) => sourcererScope.selectedPatterns.includes(kip.title))
+ );
// This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our
// own component tree instead of the embeddables (default). This is necessary to have access to
@@ -102,27 +112,30 @@ export const EmbeddedMapComponent = ({
const { services } = useKibana();
+ useEffect(() => {
+ setMapIndexPatterns((prevMapIndexPatterns) => {
+ const newIndexPatterns = kibanaIndexPatterns.filter((kip) =>
+ sourcererScope.selectedPatterns.includes(kip.title)
+ );
+ if (!deepEqual(newIndexPatterns, prevMapIndexPatterns)) {
+ if (newIndexPatterns.length === 0) {
+ setIsError(true);
+ }
+ return newIndexPatterns;
+ }
+ return prevMapIndexPatterns;
+ });
+ }, [kibanaIndexPatterns, sourcererScope.selectedPatterns]);
+
// Initial Load useEffect
useEffect(() => {
let isSubscribed = true;
async function setupEmbeddable() {
- // Ensure at least one `securitySolution:defaultIndex` kibana index pattern exists before creating embeddable
- const matchingIndexPatterns = findMatchingIndexPatterns({
- kibanaIndexPatterns,
- siemDefaultIndices,
- });
-
- if (matchingIndexPatterns.length === 0 && isSubscribed) {
- setIsLoading(false);
- setIsIndexError(true);
- return;
- }
-
// Create & set Embeddable
try {
const embeddableObject = await createEmbeddable(
filters,
- getIndexPatternTitleIdMapping(matchingIndexPatterns),
+ mapIndexPatterns,
query,
startDate,
endDate,
@@ -131,7 +144,12 @@ export const EmbeddedMapComponent = ({
services.embeddable
);
if (isSubscribed) {
- setEmbeddable(embeddableObject);
+ if (mapIndexPatterns.length === 0) {
+ setIsIndexError(true);
+ } else {
+ setEmbeddable(embeddableObject);
+ setIsIndexError(false);
+ }
}
} catch (e) {
if (isSubscribed) {
@@ -139,19 +157,41 @@ export const EmbeddedMapComponent = ({
setIsError(true);
}
}
- if (isSubscribed) {
- setIsLoading(false);
- }
}
-
- if (!loadingKibanaIndexPatterns) {
+ if (embeddable == null && sourcererScope.selectedPatterns.length > 0) {
setupEmbeddable();
}
+
return () => {
isSubscribed = false;
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]);
+ }, [
+ dispatchToaster,
+ endDate,
+ embeddable,
+ filters,
+ mapIndexPatterns,
+ query,
+ portalNode,
+ services.embeddable,
+ sourcererScope.selectedPatterns,
+ setQuery,
+ startDate,
+ ]);
+
+ // update layer with new index patterns
+ useEffect(() => {
+ const setLayerList = async () => {
+ if (embeddable != null) {
+ // @ts-expect-error
+ await embeddable.setLayerList(getLayerList(mapIndexPatterns));
+ embeddable.reload();
+ }
+ };
+ if (embeddable != null && !isErrorEmbeddable(embeddable)) {
+ setLayerList();
+ }
+ }, [embeddable, mapIndexPatterns]);
// queryExpression updated useEffect
useEffect(() => {
@@ -198,10 +238,10 @@ export const EmbeddedMapComponent = ({
- {embeddable != null ? (
-
- ) : !isLoading && isIndexError ? (
+ {isIndexError ? (
+ ) : embeddable != null ? (
+
) : (
)}
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx
new file mode 100644
index 0000000000000..d5b105dd32798
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.test.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { State } from '../../../common/store';
+
+import { getDefaultSourcererSelector } from './selector';
+
+jest.mock('../../../common/store/sourcerer', () => ({
+ sourcererSelectors: {
+ kibanaIndexPatternsSelector: jest.fn().mockReturnValue(jest.fn()),
+ scopesSelector: jest.fn().mockReturnValue(jest.fn().mockReturnValue({ default: '' })),
+ },
+}));
+
+describe('getDefaultSourcererSelector', () => {
+ test('Returns correct format', () => {
+ const mockMapStateToProps = getDefaultSourcererSelector();
+ const result = mockMapStateToProps({} as State);
+ expect(result).toHaveProperty('kibanaIndexPatterns');
+ expect(result).toHaveProperty('sourcererScope');
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx
new file mode 100644
index 0000000000000..2d0bc970f0a51
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/selector.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { State } from '../../../common/store';
+import { sourcererSelectors } from '../../../common/store/sourcerer';
+import {
+ KibanaIndexPatterns,
+ ManageScope,
+ SourcererScopeName,
+} from '../../../common/store/sourcerer/model';
+
+export interface DefaultSourcererSelector {
+ kibanaIndexPatterns: KibanaIndexPatterns;
+ sourcererScope: ManageScope;
+}
+
+export const getDefaultSourcererSelector = () => {
+ const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector();
+ const getScopesSelector = sourcererSelectors.scopesSelector();
+
+ const mapStateToProps = (state: State): DefaultSourcererSelector => {
+ const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state);
+ const scope = getScopesSelector(state)[SourcererScopeName.default];
+
+ return {
+ kibanaIndexPatterns,
+ sourcererScope: scope,
+ };
+ };
+
+ return mapStateToProps;
+};