diff --git a/packages/kbn-shared-ux-components/src/index.ts b/packages/kbn-shared-ux-components/src/index.ts
index 9216f5b21d7f5..557ac980a14c6 100644
--- a/packages/kbn-shared-ux-components/src/index.ts
+++ b/packages/kbn-shared-ux-components/src/index.ts
@@ -95,6 +95,23 @@ export const LazyIconButtonGroup = React.lazy(() =>
*/
export const IconButtonGroup = withSuspense(LazyIconButtonGroup);
+/**
+ * The lazily loaded `KibanaPageTemplateSolutionNav` component that is wrapped by the `withSuspense` HOC. Consumers should use
+ * `React.Suspense` or `withSuspense` HOC to load this component.
+ */
+export const KibanaPageTemplateSolutionNavLazy = React.lazy(() =>
+ import('./page_template/solution_nav').then(({ KibanaPageTemplateSolutionNav }) => ({
+ default: KibanaPageTemplateSolutionNav,
+ }))
+);
+
+/**
+ * A `KibanaPageTemplateSolutionNav` component that is wrapped by the `withSuspense` HOC. This component can
+ * be used directly by consumers and will load the `KibanaPageTemplateSolutionNavLazy` component lazily with
+ * a predefined fallback and error boundary.
+ */
+export const KibanaPageTemplateSolutionNav = withSuspense(KibanaPageTemplateSolutionNavLazy);
+
/**
* The Lazily-loaded `KibanaSolutionAvatar` component. Consumers should use `React.Suspense` or
* the withSuspense` HOC to load this component.
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap
new file mode 100644
index 0000000000000..fce0e996d99cd
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap
@@ -0,0 +1,267 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`KibanaPageTemplateSolutionNav accepts EuiSideNavProps 1`] = `
+
+
+
+ Solution
+
+
+ }
+ isOpenOnMobile={false}
+ items={
+ Array [
+ Object {
+ "id": "1",
+ "items": Array [
+ Object {
+ "id": "1.1",
+ "items": undefined,
+ "name": "Ingest Node Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.2",
+ "items": undefined,
+ "name": "Logstash Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.3",
+ "items": undefined,
+ "name": "Beats Central Management",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Ingest",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2",
+ "items": Array [
+ Object {
+ "id": "2.1",
+ "items": undefined,
+ "name": "Index Management",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.2",
+ "items": undefined,
+ "name": "Index Lifecycle Policies",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.3",
+ "items": undefined,
+ "name": "Snapshot and Restore",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Data",
+ "tabIndex": undefined,
+ },
+ ]
+ }
+ mobileTitle={
+
+
+
+ }
+ toggleOpenOnMobile={[Function]}
+ />
+
+`;
+
+exports[`KibanaPageTemplateSolutionNav renders 1`] = `
+
+
+
+ Solution
+
+
+ }
+ isOpenOnMobile={false}
+ items={
+ Array [
+ Object {
+ "id": "1",
+ "items": Array [
+ Object {
+ "id": "1.1",
+ "items": undefined,
+ "name": "Ingest Node Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.2",
+ "items": undefined,
+ "name": "Logstash Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.3",
+ "items": undefined,
+ "name": "Beats Central Management",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Ingest",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2",
+ "items": Array [
+ Object {
+ "id": "2.1",
+ "items": undefined,
+ "name": "Index Management",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.2",
+ "items": undefined,
+ "name": "Index Lifecycle Policies",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.3",
+ "items": undefined,
+ "name": "Snapshot and Restore",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Data",
+ "tabIndex": undefined,
+ },
+ ]
+ }
+ mobileTitle={
+
+
+
+ }
+ toggleOpenOnMobile={[Function]}
+ />
+
+`;
+
+exports[`KibanaPageTemplateSolutionNav renders with icon 1`] = `
+
+
+
+
+ Solution
+
+
+ }
+ isOpenOnMobile={false}
+ items={
+ Array [
+ Object {
+ "id": "1",
+ "items": Array [
+ Object {
+ "id": "1.1",
+ "items": undefined,
+ "name": "Ingest Node Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.2",
+ "items": undefined,
+ "name": "Logstash Pipelines",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "1.3",
+ "items": undefined,
+ "name": "Beats Central Management",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Ingest",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2",
+ "items": Array [
+ Object {
+ "id": "2.1",
+ "items": undefined,
+ "name": "Index Management",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.2",
+ "items": undefined,
+ "name": "Index Lifecycle Policies",
+ "tabIndex": undefined,
+ },
+ Object {
+ "id": "2.3",
+ "items": undefined,
+ "name": "Snapshot and Restore",
+ "tabIndex": undefined,
+ },
+ ],
+ "name": "Data",
+ "tabIndex": undefined,
+ },
+ ]
+ }
+ mobileTitle={
+
+
+
+
+ }
+ toggleOpenOnMobile={[Function]}
+ />
+
+`;
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav_collapse_button.test.tsx.snap b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav_collapse_button.test.tsx.snap
new file mode 100644
index 0000000000000..d2548b3e8df43
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav_collapse_button.test.tsx.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`KibanaPageTemplateSolutionNavCollapseButton isCollapsed 1`] = `
+
+`;
+
+exports[`KibanaPageTemplateSolutionNavCollapseButton renders 1`] = `
+
+`;
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/index.ts b/packages/kbn-shared-ux-components/src/page_template/solution_nav/index.ts
new file mode 100644
index 0000000000000..59ef2924b048d
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/index.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export type { KibanaPageTemplateSolutionNavProps } from './solution_nav';
+export { KibanaPageTemplateSolutionNav } from './solution_nav';
+export type { KibanaPageTemplateSolutionNavCollapseButtonProps } from './solution_nav_collapse_button';
+export { KibanaPageTemplateSolutionNavCollapseButton } from './solution_nav_collapse_button';
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.scss b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.scss
new file mode 100644
index 0000000000000..d0070cef729b7
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.scss
@@ -0,0 +1,30 @@
+$euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7);
+@import '@elastic/eui/src/components/side_nav/mixins';
+
+// Put the page background color in the flyout version too
+.kbnPageTemplateSolutionNav__flyout {
+ background-color: $euiPageBackgroundColor;
+}
+
+.kbnPageTemplateSolutionNav {
+ @include euiSideNavEmbellish;
+ @include euiYScroll;
+
+ @include euiBreakpoint('m' ,'l', 'xl') {
+ width: 248px;
+ padding: $euiSizeL;
+ }
+
+ .kbnPageTemplateSolutionNavAvatar {
+ margin-right: $euiSize;
+ }
+}
+
+.kbnPageTemplateSolutionNav--hidden {
+ pointer-events: none;
+ opacity: 0;
+
+ @include euiCanAnimate {
+ transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance;
+ }
+}
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx
new file mode 100644
index 0000000000000..5ff1e2c07d9d8
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx
@@ -0,0 +1,72 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { KibanaPageTemplateSolutionNav, KibanaPageTemplateSolutionNavProps } from './solution_nav';
+
+export default {
+ title: 'Page Template/Solution Nav/Solution Nav',
+ description: 'Solution-specific navigation for the sidebar',
+};
+
+type Params = Pick;
+
+const items: KibanaPageTemplateSolutionNavProps['items'] = [
+ {
+ name: Ingest
,
+ id: '1',
+ items: [
+ {
+ name: 'Ingest Node Pipelines',
+ id: '1.1',
+ },
+ {
+ name: 'Logstash Pipelines',
+ id: '1.2',
+ },
+ {
+ name: 'Beats Central Management',
+ id: '1.3',
+ },
+ ],
+ },
+ {
+ name: 'Data',
+ id: '2',
+ items: [
+ {
+ name: 'Index Management',
+ id: '2.1',
+ },
+ {
+ name: 'Index Lifecycle Policies',
+ id: '2.2',
+ },
+ {
+ name: 'Snapshot and Restore',
+ id: '2.3',
+ },
+ ],
+ },
+];
+
+export const PureComponent = (params: Params) => {
+ return ;
+};
+
+PureComponent.argTypes = {
+ name: {
+ control: 'text',
+ defaultValue: 'Kibana',
+ },
+ icon: {
+ control: { type: 'radio' },
+ options: ['logoKibana', 'logoObservability', 'logoSecurity'],
+ defaultValue: 'logoKibana',
+ },
+};
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx
new file mode 100644
index 0000000000000..ed90894289169
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx
@@ -0,0 +1,77 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { KibanaPageTemplateSolutionNav, KibanaPageTemplateSolutionNavProps } from './solution_nav';
+
+jest.mock('@elastic/eui', () => ({
+ useIsWithinBreakpoints: (args: string[]) => {
+ return args[0] === 'xs';
+ },
+}));
+
+const items: KibanaPageTemplateSolutionNavProps['items'] = [
+ {
+ name: 'Ingest',
+ id: '1',
+ items: [
+ {
+ name: 'Ingest Node Pipelines',
+ id: '1.1',
+ },
+ {
+ name: 'Logstash Pipelines',
+ id: '1.2',
+ },
+ {
+ name: 'Beats Central Management',
+ id: '1.3',
+ },
+ ],
+ },
+ {
+ name: 'Data',
+ id: '2',
+ items: [
+ {
+ name: 'Index Management',
+ id: '2.1',
+ },
+ {
+ name: 'Index Lifecycle Policies',
+ id: '2.2',
+ },
+ {
+ name: 'Snapshot and Restore',
+ id: '2.3',
+ },
+ ],
+ },
+];
+
+describe('KibanaPageTemplateSolutionNav', () => {
+ test('renders', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('renders with icon', () => {
+ const component = shallow(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+
+ test('accepts EuiSideNavProps', () => {
+ const component = shallow(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx
new file mode 100644
index 0000000000000..8bc91789c7054
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import './solution_nav.scss';
+
+import React, { FunctionComponent, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import {
+ EuiAvatarProps,
+ EuiFlyout,
+ EuiSideNav,
+ EuiSideNavItemType,
+ EuiSideNavProps,
+ useIsWithinBreakpoints,
+} from '@elastic/eui';
+
+import classNames from 'classnames';
+import { KibanaSolutionAvatar } from '../../solution_avatar';
+import { KibanaPageTemplateSolutionNavCollapseButton } from './solution_nav_collapse_button';
+
+export type KibanaPageTemplateSolutionNavProps = EuiSideNavProps<{}> & {
+ /**
+ * Name of the solution, i.e. "Observability"
+ */
+ name: EuiAvatarProps['name'];
+ /**
+ * Solution logo, i.e. "logoObservability"
+ */
+ icon?: EuiAvatarProps['iconType'];
+ /**
+ * Control the collapsed state
+ */
+ isOpenOnDesktop?: boolean;
+ onCollapse?: () => void;
+};
+
+const FLYOUT_SIZE = 248;
+
+const setTabIndex = (items: Array>, isHidden: boolean) => {
+ return items.map((item) => {
+ // @ts-ignore-next-line Can be removed on close of https://github.com/elastic/eui/issues/4925
+ item.tabIndex = isHidden ? -1 : undefined;
+ item.items = item.items && setTabIndex(item.items, isHidden);
+ return item;
+ });
+};
+
+/**
+ * A wrapper around EuiSideNav but also creates the appropriate title with optional solution logo
+ */
+export const KibanaPageTemplateSolutionNav: FunctionComponent<
+ KibanaPageTemplateSolutionNavProps
+> = ({ name, icon, items, isOpenOnDesktop = false, onCollapse, ...rest }) => {
+ const isSmallerBreakpoint = useIsWithinBreakpoints(['xs', 's']);
+ const isMediumBreakpoint = useIsWithinBreakpoints(['m']);
+ const isLargerBreakpoint = useIsWithinBreakpoints(['l', 'xl']);
+
+ // This is used for both the EuiSideNav and EuiFlyout toggling
+ const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
+ const toggleOpenOnMobile = () => {
+ setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile);
+ };
+
+ const isHidden = isLargerBreakpoint && !isOpenOnDesktop;
+
+ /**
+ * Create the avatar
+ */
+ const solutionAvatar = icon ? (
+
+ ) : null;
+
+ /**
+ * Create the titles
+ */
+ const titleText = (
+ <>
+ {solutionAvatar}
+ {name}
+ >
+ );
+ const mobileTitleText = (
+
+ );
+
+ /**
+ * Create the side nav component
+ */
+
+ const sideNav = () => {
+ if (!items) {
+ return null;
+ }
+ const sideNavClasses = classNames('kbnPageTemplateSolutionNav', {
+ 'kbnPageTemplateSolutionNav--hidden': isHidden,
+ });
+ return (
+
+ {solutionAvatar}
+ {mobileTitleText}
+ >
+ }
+ toggleOpenOnMobile={toggleOpenOnMobile}
+ isOpenOnMobile={isSideNavOpenOnMobile}
+ items={setTabIndex(items, isHidden)}
+ {...rest}
+ />
+ );
+ };
+
+ return (
+ <>
+ {isSmallerBreakpoint && sideNav()}
+ {isMediumBreakpoint && (
+ <>
+ {isSideNavOpenOnMobile && (
+ setIsSideNavOpenOnMobile(false)}
+ side="left"
+ size={FLYOUT_SIZE}
+ closeButtonPosition="outside"
+ className="kbnPageTemplateSolutionNav__flyout"
+ >
+ {sideNav()}
+
+ )}
+
+ >
+ )}
+ {isLargerBreakpoint && (
+ <>
+ {sideNav()}
+
+ >
+ )}
+ >
+ );
+};
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.scss b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.scss
new file mode 100644
index 0000000000000..61cea7962d956
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.scss
@@ -0,0 +1,47 @@
+.kbnPageTemplateSolutionNavCollapseButton {
+ position: absolute;
+ opacity: 0;
+ left: 248px - $euiSize;
+ top: $euiSizeL;
+ z-index: 2;
+
+ @include euiCanAnimate {
+ transition: opacity $euiAnimSpeedFast, left $euiAnimSpeedFast, background $euiAnimSpeedFast;
+ }
+
+ &:hover,
+ &:focus {
+ transition-delay: 0s !important;
+ }
+
+ .kbnPageTemplate__pageSideBar:hover &,
+ &:hover,
+ &:focus {
+ opacity: 1;
+ left: 248px - $euiSizeL;
+ }
+
+ .kbnPageTemplate__pageSideBar:hover & {
+ transition-delay: $euiAnimSpeedSlow * 2;
+ }
+
+ &:not(&-isCollapsed) {
+ background-color: $euiColorEmptyShade !important; // Override all states
+ }
+}
+
+// Make the button take up the entire area of the collapsed navigation
+.kbnPageTemplateSolutionNavCollapseButton-isCollapsed {
+ opacity: 1 !important;
+ transition-delay: 0s !important;
+ left: 0 !important;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ border-radius: 0;
+ // Keep the icon at the top instead of it getting shifted to the center of the page
+ padding-top: $euiSizeL + $euiSizeS;
+ align-items: flex-start;
+}
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.test.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.test.tsx
new file mode 100644
index 0000000000000..e7df2ddd54582
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.test.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import { KibanaPageTemplateSolutionNavCollapseButton } from './solution_nav_collapse_button';
+
+describe('KibanaPageTemplateSolutionNavCollapseButton', () => {
+ test('renders', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ expect(component.find('.kbnPageTemplateSolutionNavCollapseButton').prop('title')).toBe(
+ 'Collapse side navigation'
+ );
+ });
+
+ test('isCollapsed', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ expect(component.find('.kbnPageTemplateSolutionNavCollapseButton').prop('title')).toBe(
+ 'Open side navigation'
+ );
+ });
+});
diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.tsx
new file mode 100644
index 0000000000000..35890b935ad3e
--- /dev/null
+++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav_collapse_button.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import './solution_nav_collapse_button.scss';
+
+import React from 'react';
+import classNames from 'classnames';
+
+import { EuiButtonIcon, EuiButtonIconPropsForButton } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+export type KibanaPageTemplateSolutionNavCollapseButtonProps =
+ Partial & {
+ /**
+ * Boolean state of current collapsed status
+ */
+ isCollapsed: boolean;
+ };
+
+const collapseLabel = i18n.translate('sharedUXComponents.solutionNav.collapsibleLabel', {
+ defaultMessage: 'Collapse side navigation',
+});
+
+const openLabel = i18n.translate('sharedUXComponents.solutionNav.openLabel', {
+ defaultMessage: 'Open side navigation',
+});
+
+/**
+ * Creates the styled icon button for showing/hiding solution nav
+ */
+export const KibanaPageTemplateSolutionNavCollapseButton = ({
+ className,
+ isCollapsed,
+ ...rest
+}: KibanaPageTemplateSolutionNavCollapseButtonProps) => {
+ const classes = classNames(
+ 'kbnPageTemplateSolutionNavCollapseButton',
+ {
+ 'kbnPageTemplateSolutionNavCollapseButton-isCollapsed': isCollapsed,
+ },
+ className
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/kbn-shared-ux-components/src/solution_avatar/index.tsx b/packages/kbn-shared-ux-components/src/solution_avatar/index.tsx
index db31c0fd5a3d4..efc597cbdcb13 100644
--- a/packages/kbn-shared-ux-components/src/solution_avatar/index.tsx
+++ b/packages/kbn-shared-ux-components/src/solution_avatar/index.tsx
@@ -5,5 +5,5 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-
export { KibanaSolutionAvatar } from './solution_avatar';
+export type { KibanaSolutionAvatarProps } from './solution_avatar';
diff --git a/packages/kbn-shared-ux-components/src/solution_avatar/solution_avatar.tsx b/packages/kbn-shared-ux-components/src/solution_avatar/solution_avatar.tsx
index 78459b90e4b3b..deb71affc9c1a 100644
--- a/packages/kbn-shared-ux-components/src/solution_avatar/solution_avatar.tsx
+++ b/packages/kbn-shared-ux-components/src/solution_avatar/solution_avatar.tsx
@@ -8,9 +8,9 @@
import './solution_avatar.scss';
import React from 'react';
-import classNames from 'classnames';
import { DistributiveOmit, EuiAvatar, EuiAvatarProps } from '@elastic/eui';
+import classNames from 'classnames';
export type KibanaSolutionAvatarProps = DistributiveOmit & {
/**
@@ -20,7 +20,7 @@ export type KibanaSolutionAvatarProps = DistributiveOmit
};
/**
- * Applies extra styling to a typical EuiAvatar;
+ * Applies extra styling to a typical EuiAvatar.
* The `name` value will be appended to 'logo' to configure the `iconType` unless `iconType` is provided.
*/
export const KibanaSolutionAvatar = ({ className, size, ...rest }: KibanaSolutionAvatarProps) => {
@@ -34,9 +34,9 @@ export const KibanaSolutionAvatar = ({ className, size, ...rest }: KibanaSolutio
},
className
)}
- color="plain"
size={size === 'xxl' ? 'xl' : size}
iconSize={size}
+ color="plain"
iconType={`logo${rest.name}`}
{...rest}
/>
diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx
index 498efcfd9076e..54adb34550462 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx
@@ -89,7 +89,7 @@ const overviewIDsToHide = ['kibanaOverview', 'enterpriseSearch'];
const overviewIDs = [
...overviewIDsToHide,
'observability-overview',
- 'securitySolutionUI:overview',
+ 'securitySolutionUI:get_started',
'management',
];
diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
index 6b417a984d899..a6eb2f04a6c75 100644
--- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts
+++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts
@@ -97,7 +97,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
id: SecurityPageName.landing,
title: GETTING_STARTED,
path: LANDING_PATH,
- navLinkStatus: AppNavLinkStatus.visible,
+ navLinkStatus: AppNavLinkStatus.hidden,
features: [FEATURE.general],
keywords: [
i18n.translate('xpack.securitySolution.search.getStarted', {
diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts
index 1ae5544dbd740..02727084020b5 100644
--- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts
+++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts
@@ -34,13 +34,6 @@ import {
} from '../../../common/constants';
export const navTabs: SecurityNav = {
- [SecurityPageName.overview]: {
- id: SecurityPageName.overview,
- name: i18n.OVERVIEW,
- href: APP_OVERVIEW_PATH,
- disabled: false,
- urlKey: 'overview',
- },
[SecurityPageName.landing]: {
id: SecurityPageName.landing,
name: i18n.GETTING_STARTED,
@@ -48,6 +41,13 @@ export const navTabs: SecurityNav = {
disabled: false,
urlKey: 'get_started',
},
+ [SecurityPageName.overview]: {
+ id: SecurityPageName.overview,
+ name: i18n.OVERVIEW,
+ href: APP_OVERVIEW_PATH,
+ disabled: false,
+ urlKey: 'overview',
+ },
[SecurityPageName.detectionAndResponse]: {
id: SecurityPageName.detectionAndResponse,
name: i18n.DETECTION_RESPONSE,
diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx
index deddea9520c3d..b9f18e7335a4d 100644
--- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx
+++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx
@@ -79,7 +79,9 @@ export const SecuritySolutionTemplateWrapper: React.FC
- {showEmptyState ? (
- children
- ) : (
- <>
-
-
- {children}
-
- >
- )}
+ <>
+
+
+ {children}
+
+ >
);
});
diff --git a/x-pack/plugins/security_solution/public/overview/components/landing_cards/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_cards/index.tsx
similarity index 98%
rename from x-pack/plugins/security_solution/public/overview/components/landing_cards/index.tsx
rename to x-pack/plugins/security_solution/public/common/components/landing_cards/index.tsx
index d8852d8603518..20ad45da680ec 100644
--- a/x-pack/plugins/security_solution/public/overview/components/landing_cards/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/landing_cards/index.tsx
@@ -22,7 +22,7 @@ import endpointPng from '../../images/endpoint.png';
import siemPng from '../../images/siem.png';
import videoSvg from '../../images/video.svg';
import { ADD_DATA_PATH } from '../../../../common/constants';
-import { useKibana } from '../../../common/lib/kibana';
+import { useKibana } from '../../lib/kibana';
const imgUrls = {
siem: siemPng,
diff --git a/x-pack/plugins/security_solution/public/overview/components/landing_cards/translations.tsx b/x-pack/plugins/security_solution/public/common/components/landing_cards/translations.tsx
similarity index 100%
rename from x-pack/plugins/security_solution/public/overview/components/landing_cards/translations.tsx
rename to x-pack/plugins/security_solution/public/common/components/landing_cards/translations.tsx
diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/landing_page/__snapshots__/index.test.tsx.snap
new file mode 100644
index 0000000000000..d55ce293d69df
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/landing_page/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LandingPageComponent component renders page properly 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/index.test.tsx
new file mode 100644
index 0000000000000..0e27280cc2c4f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/landing_page/index.test.tsx
@@ -0,0 +1,18 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { LandingPageComponent } from './index';
+
+describe('LandingPageComponent component', () => {
+ it('renders page properly', () => {
+ const EmptyComponent = shallow();
+ expect(EmptyComponent).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx
new file mode 100644
index 0000000000000..310420524040d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx
@@ -0,0 +1,20 @@
+/*
+ * 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, { memo } from 'react';
+import { LandingCards } from '../landing_cards';
+import { SecuritySolutionPageWrapper } from '../page_wrapper';
+
+export const LandingPageComponent = memo(() => {
+ return (
+
+
+
+ );
+});
+
+LandingPageComponent.displayName = 'LandingPageComponent';
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
index baf4965467b51..15ac66ffddfd1 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts
@@ -8,7 +8,7 @@
import { SecurityPageName } from '../../../../app/types';
export { getDetectionEngineUrl } from '../redirect_to_detection_engine';
-export { getAppOverviewUrl } from '../redirect_to_overview';
+export { getAppLandingUrl } from '../redirect_to_landing';
export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network';
export { getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines';
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
index 2f7f876bda9bc..2f9b3542d765b 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts
@@ -14,7 +14,6 @@ import { SecurityNavKey } from '../navigation/types';
import { SecurityPageName } from '../../../app/types';
export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detection_engine';
-export { getAppOverviewUrl } from './redirect_to_overview';
export { getHostDetailsUrl, getTabsOnHostDetailsUrl, getHostsUrl } from './redirect_to_hosts';
export { getNetworkUrl, getNetworkDetailsUrl } from './redirect_to_network';
export { getTimelineTabsUrl, getTimelineUrl } from './redirect_to_timelines';
diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_landing.tsx
similarity index 51%
rename from x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx
rename to x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_landing.tsx
index 6a83edd7442de..10ef61da0b814 100644
--- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_overview.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_landing.tsx
@@ -6,9 +6,5 @@
*/
import { appendSearch } from './helpers';
-import { LANDING_PATH } from '../../../../common/constants';
-export const getAppOverviewUrl = (overviewPath: string, search?: string) =>
- `${overviewPath}${appendSearch(search)}`;
-
-export const getAppLandingUrl = (search?: string) => `${LANDING_PATH}${appendSearch(search)}`;
+export const getAppLandingUrl = (path: string, search?: string) => `${path}${appendSearch(search)}`;
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
index 5639721403cbd..7d2bfaa405cb2 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
@@ -158,7 +158,7 @@ describe('Navigation Breadcrumbs', () => {
);
expect(breadcrumbs).toEqual([
{
- href: 'securitySolutionUI/overview',
+ href: 'securitySolutionUI/get_started',
text: 'Security',
},
{
@@ -178,7 +178,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Network',
href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -196,7 +196,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Timelines',
href: "securitySolutionUI/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -210,7 +210,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Hosts',
href: "securitySolutionUI/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -229,7 +229,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Network',
href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -248,7 +248,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Network',
href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -267,7 +267,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Alerts',
href: '',
@@ -281,7 +281,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Exceptions',
href: '',
@@ -295,7 +295,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Rules',
href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -309,7 +309,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Rules',
href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -335,7 +335,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Rules',
href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -361,7 +361,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Rules',
href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
@@ -406,7 +406,7 @@ describe('Navigation Breadcrumbs', () => {
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
- { text: 'Security', href: 'securitySolutionUI/overview' },
+ { text: 'Security', href: 'securitySolutionUI/get_started' },
{
text: 'Endpoints',
href: '',
@@ -428,7 +428,7 @@ describe('Navigation Breadcrumbs', () => {
expect(setBreadcrumbsMock).toBeCalledWith([
expect.objectContaining({
text: 'Security',
- href: 'securitySolutionUI/overview',
+ href: 'securitySolutionUI/get_started',
onClick: expect.any(Function),
}),
expect.objectContaining({
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
index 83ee6ce481250..fead76ff1b2eb 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts
@@ -26,7 +26,7 @@ import {
AdministrationRouteSpyState,
UsersRouteSpyState,
} from '../../../utils/route/types';
-import { getAppOverviewUrl } from '../../link_to';
+import { getAppLandingUrl } from '../../link_to/redirect_to_landing';
import { timelineActions } from '../../../../../public/timelines/store/timeline';
import { TimelineId } from '../../../../../common/types/timeline';
import { TabNavigationProps } from '../tab_navigation/types';
@@ -91,10 +91,11 @@ export const getBreadcrumbsForRoute = (
getUrlForApp: GetUrlForApp
): ChromeBreadcrumb[] | null => {
const spyState: RouteSpyState = omit('navTabs', object);
- const overviewPath = getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.overview });
+ const landingPath = getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.landing });
+
const siemRootBreadcrumb: ChromeBreadcrumb = {
text: APP_NAME,
- href: getAppOverviewUrl(overviewPath),
+ href: getAppLandingUrl(landingPath),
};
if (isHostsRoutes(spyState) && object.navTabs) {
const tempNav: SearchNavTab = { urlKey: 'host', isDetailPage: false };
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
index 601794dd25917..c76ba7a43cb30 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
@@ -115,16 +115,6 @@ describe('useSecuritySolutionNavigation', () => {
Object {
"id": "main",
"items": Array [
- Object {
- "data-href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
- "data-test-subj": "navigation-overview",
- "disabled": false,
- "href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
- "id": "overview",
- "isSelected": false,
- "name": "Overview",
- "onClick": [Function],
- },
Object {
"data-href": "securitySolutionUI/get_started?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
"data-test-subj": "navigation-get_started",
@@ -135,6 +125,16 @@ describe('useSecuritySolutionNavigation', () => {
"name": "Getting started",
"onClick": [Function],
},
+ Object {
+ "data-href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
+ "data-test-subj": "navigation-overview",
+ "disabled": false,
+ "href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
+ "id": "overview",
+ "isSelected": false,
+ "name": "Overview",
+ "onClick": [Function],
+ },
],
"name": "",
},
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
index 14b007be4764d..7985f5244e84f 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
@@ -77,8 +77,8 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
id: 'main',
name: '',
items: [
- navTabs[SecurityPageName.overview],
navTabs[SecurityPageName.landing],
+ navTabs[SecurityPageName.overview],
// Temporary check for detectionAndResponse while page is feature flagged
...(navTabs[SecurityPageName.detectionAndResponse] != null
? [navTabs[SecurityPageName.detectionAndResponse]]
diff --git a/x-pack/plugins/security_solution/public/overview/images/endpoint.png b/x-pack/plugins/security_solution/public/common/images/endpoint.png
similarity index 100%
rename from x-pack/plugins/security_solution/public/overview/images/endpoint.png
rename to x-pack/plugins/security_solution/public/common/images/endpoint.png
diff --git a/x-pack/plugins/security_solution/public/overview/images/siem.png b/x-pack/plugins/security_solution/public/common/images/siem.png
similarity index 100%
rename from x-pack/plugins/security_solution/public/overview/images/siem.png
rename to x-pack/plugins/security_solution/public/common/images/siem.png
diff --git a/x-pack/plugins/security_solution/public/overview/images/video.svg b/x-pack/plugins/security_solution/public/common/images/video.svg
similarity index 100%
rename from x-pack/plugins/security_solution/public/overview/images/video.svg
rename to x-pack/plugins/security_solution/public/common/images/video.svg
diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx
index 01dbfbed6a0c2..b46b3b4177239 100644
--- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx
@@ -13,13 +13,17 @@ jest.mock('../route/use_route_spy', () => ({
.fn()
.mockImplementationOnce(() => [{ pageName: 'hosts' }])
.mockImplementationOnce(() => [{ pageName: 'rules' }])
- .mockImplementationOnce(() => [{ pageName: 'network' }]),
+ .mockImplementationOnce(() => [{ pageName: 'network' }])
+ .mockImplementationOnce(() => [{ pageName: 'get_started' }])
+ .mockImplementationOnce(() => [{ pageName: 'get_started' }]),
}));
jest.mock('../../../common/containers/sourcerer', () => ({
useSourcererDataView: jest
.fn()
.mockImplementationOnce(() => [{ indicesExist: false }])
.mockImplementationOnce(() => [{ indicesExist: false }])
+ .mockImplementationOnce(() => [{ indicesExist: true }])
+ .mockImplementationOnce(() => [{ indicesExist: false }])
.mockImplementationOnce(() => [{ indicesExist: true }]),
}));
@@ -48,4 +52,22 @@ describe('use show pages with empty view', () => {
expect(emptyResult).toEqual(true);
});
});
+
+ it('apply empty view for the landing page if indices do not exist', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
+ await waitForNextUpdate();
+ const emptyResult = result.current;
+ expect(emptyResult).toEqual(true);
+ });
+ });
+
+ it('apply empty view for the landing page if indices exist', async () => {
+ await act(async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
+ await waitForNextUpdate();
+ const emptyResult = result.current;
+ expect(emptyResult).toEqual(true);
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx
index f863cecffe3d6..3ef27addb8efe 100644
--- a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx
@@ -25,7 +25,8 @@ export const useShowPagesWithEmptyView = () => {
const [{ pageName }] = useRouteSpy();
const { indicesExist } = useSourcererDataView();
- const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist;
+ const shouldShowEmptyState =
+ (isPageNameWithEmptyView(pageName) && !indicesExist) || pageName === SecurityPageName.landing;
const [showEmptyState, setShowEmptyState] = useState(shouldShowEmptyState);
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index 5bd6875032e03..3fc05c0ef428f 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -40,7 +40,6 @@ import { AlertsTable } from '../../components/alerts_table';
import { NoApiIntegrationKeyCallOut } from '../../components/callouts/no_api_integration_callout';
import { AlertsHistogramPanel } from '../../components/alerts_kpis/alerts_histogram_panel';
import { useUserData } from '../../components/user_info';
-import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { DetectionEngineNoIndex } from './detection_engine_no_index';
import { useListsConfig } from '../../containers/detection_engine/lists/use_lists_config';
import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated';
@@ -77,6 +76,7 @@ import {
} from '../../components/alerts_table/alerts_filter_group';
import { EmptyPage } from '../../../common/components/empty_page';
import { HeaderPage } from '../../../common/components/header_page';
+import { LandingPageComponent } from '../../../common/components/landing_page';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
*/
@@ -389,10 +389,7 @@ const DetectionEnginePageComponent: React.FC = ({
) : (
-
-
-
-
+
)}
>
);
diff --git a/x-pack/plugins/security_solution/public/helpers.test.tsx b/x-pack/plugins/security_solution/public/helpers.test.tsx
index dd380fc3ccd39..4f39ea32c6d5d 100644
--- a/x-pack/plugins/security_solution/public/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/helpers.test.tsx
@@ -193,7 +193,7 @@ describe('RedirectRoute', () => {
} as unknown as Capabilities;
expect(shallow()).toMatchInlineSnapshot(`
`);
});
@@ -205,7 +205,7 @@ describe('RedirectRoute', () => {
} as unknown as Capabilities;
expect(shallow()).toMatchInlineSnapshot(`
`);
});
@@ -217,7 +217,7 @@ describe('RedirectRoute', () => {
} as unknown as Capabilities;
expect(shallow()).toMatchInlineSnapshot(`
`);
});
@@ -229,7 +229,7 @@ describe('RedirectRoute', () => {
} as unknown as Capabilities;
expect(shallow()).toMatchInlineSnapshot(`
`);
});
@@ -241,7 +241,7 @@ describe('RedirectRoute', () => {
} as unknown as Capabilities;
expect(shallow()).toMatchInlineSnapshot(`
`);
});
diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx
index fa0b8d115ea40..adc22b96d2050 100644
--- a/x-pack/plugins/security_solution/public/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/helpers.tsx
@@ -18,7 +18,7 @@ import {
RULES_PATH,
SERVER_APP_ID,
CASES_FEATURE_ID,
- OVERVIEW_PATH,
+ LANDING_PATH,
CASES_PATH,
} from '../common/constants';
import { Ecs } from '../common/ecs';
@@ -138,7 +138,7 @@ export const manageOldSiemRoutes = async (coreStart: CoreStart) => {
break;
default:
application.navigateToApp(APP_UI_ID, {
- deepLinkId: SecurityPageName.overview,
+ deepLinkId: SecurityPageName.landing,
replace: true,
path,
});
@@ -197,12 +197,12 @@ export const RedirectRoute = React.memo<{ capabilities: Capabilities }>(({ capab
const overviewAvailable = isSubPluginAvailable('overview', capabilities);
const casesAvailable = isSubPluginAvailable(CASES_SUB_PLUGIN_KEY, capabilities);
if (overviewAvailable) {
- return ;
+ return ;
}
if (casesAvailable) {
return ;
}
- return ;
+ return ;
});
RedirectRoute.displayName = 'RedirectRoute';
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 45daa31a4ec37..f1b03c7a3c4c4 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
@@ -36,7 +36,6 @@ import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions
import { SpyRoute } from '../../../common/utils/route/spy_routes';
import { getEsQueryConfig } from '../../../../../../../src/plugins/data/common';
-import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { HostDetailsTabs } from './details_tabs';
import { navTabsHostDetails } from './nav_tabs';
import { HostDetailsProps } from './types';
@@ -54,6 +53,7 @@ import { manageQuery } from '../../../common/components/page/manage_query';
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
+import { LandingPageComponent } from '../../../common/components/landing_page';
const HostOverviewManage = manageQuery(HostOverview);
@@ -227,9 +227,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta
>
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
index d82189ab1e3bb..bdf249dc07526 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
@@ -25,8 +25,7 @@ import { Hosts } from './hosts';
import { HostsTabs } from './hosts_tabs';
import { useSourcererDataView } from '../../common/containers/sourcerer';
import { mockCasesContract } from '../../../../cases/public/mocks';
-import { APP_UI_ID, SecurityPageName } from '../../../common/constants';
-import { getAppLandingUrl } from '../../common/components/link_to/redirect_to_overview';
+import { LandingPageComponent } from '../../common/components/landing_page';
jest.mock('../../common/containers/sourcerer');
@@ -93,17 +92,15 @@ describe('Hosts - rendering', () => {
indicesExist: false,
});
- mount(
+ const wrapper = mount(
);
- expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
+
+ expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
});
test('it DOES NOT render the Setup Instructions text when an index is available', async () => {
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 3b57a22d15a6a..8d2255655b685 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -35,7 +35,6 @@ import { setAbsoluteRangeDatePicker } from '../../common/store/inputs/actions';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { getEsQueryConfig } from '../../../../../../src/plugins/data/common';
import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities';
-import { OverviewEmpty } from '../../overview/components/overview_empty';
import { Display } from './display';
import { HostsTabs } from './hosts_tabs';
import { navTabsHosts } from './nav_tabs';
@@ -56,6 +55,8 @@ import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_que
import { ID } from '../containers/hosts';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
import { filterHostExternalAlertData } from '../../common/components/visualization_actions/utils';
+import { LandingPageComponent } from '../../common/components/landing_page';
+import { Loader } from '../../common/components/loader';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -128,7 +129,8 @@ const HostsComponent = () => {
},
[dispatch]
);
- const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererDataView();
+ const { docValueFields, indicesExist, indexPattern, selectedPatterns, loading } =
+ useSourcererDataView();
const [filterQuery, kqlError] = useMemo(
() =>
convertToBuildEsQuery({
@@ -179,6 +181,10 @@ const HostsComponent = () => {
[containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable]
);
+ if (loading) {
+ return ;
+ }
+
return (
<>
{indicesExist ? (
@@ -240,9 +246,7 @@ const HostsComponent = () => {
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
index ef7dea5164468..f8aa0a5d85730 100644
--- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
@@ -38,7 +38,6 @@ import { inputsSelectors } from '../../../common/store';
import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions';
import { setNetworkDetailsTablesActivePageToZero } from '../../store/actions';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
-import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { NetworkHttpQueryTable } from './network_http_query_table';
import { NetworkTopCountriesQueryTable } from './network_top_countries_query_table';
import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table';
@@ -50,6 +49,7 @@ import { networkModel } from '../../store';
import { SecurityPageName } from '../../../app/types';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
+import { LandingPageComponent } from '../../../common/components/landing_page';
export { getBreadcrumbs } from './utils';
const NetworkDetailsManage = manageQuery(IpOverview);
@@ -301,9 +301,7 @@ const NetworkDetailsComponent: React.FC = () => {
>
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx
index 23cd7f707dfe8..bf300569d6e23 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx
@@ -25,8 +25,7 @@ import { inputsActions } from '../../common/store/inputs';
import { Network } from './network';
import { NetworkRoutes } from './navigation';
import { mockCasesContract } from '../../../../cases/public/mocks';
-import { APP_UI_ID, SecurityPageName } from '../../../common/constants';
-import { getAppLandingUrl } from '../../common/components/link_to/redirect_to_overview';
+import { LandingPageComponent } from '../../common/components/landing_page';
jest.mock('../../common/containers/sourcerer');
@@ -119,13 +118,13 @@ describe('Network page - rendering', () => {
beforeEach(() => {
jest.clearAllMocks();
});
- test('it renders the Setup Instructions text when no index is available', () => {
+ test('it renders getting started page when no index is available', () => {
mockUseSourcererDataView.mockReturnValue({
selectedPatterns: [],
indicesExist: false,
});
- mount(
+ const wrapper = mount(
@@ -133,13 +132,10 @@ describe('Network page - rendering', () => {
);
- expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
+ expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
});
- test('it DOES NOT render the Setup Instructions text when an index is available', async () => {
+ test('it DOES NOT render getting started page when an index is available', async () => {
mockUseSourcererDataView.mockReturnValue({
selectedPatterns: [],
indicesExist: true,
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 422d2877a8504..634a96b0e74df 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx
@@ -36,7 +36,6 @@ import { SpyRoute } from '../../common/utils/route/spy_routes';
import { Display } from '../../hosts/pages/display';
import { networkModel } from '../store';
import { navTabsNetwork, NetworkRoutes, NetworkRoutesLoading } from './navigation';
-import { OverviewEmpty } from '../../overview/components/overview_empty';
import * as i18n from './translations';
import { NetworkComponentProps } from './types';
import { NetworkRouteType } from './navigation/types';
@@ -52,6 +51,7 @@ import { useSourcererDataView } from '../../common/containers/sourcerer';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector';
import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query';
import { filterNetworkExternalAlertData } from '../../common/components/visualization_actions/utils';
+import { LandingPageComponent } from '../../common/components/landing_page';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
*/
@@ -234,9 +234,7 @@ const NetworkComponent = React.memo(
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx
deleted file mode 100644
index db157e9fc7135..0000000000000
--- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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 { shallow } from 'enzyme';
-import { OverviewEmpty } from '.';
-import { APP_UI_ID, SecurityPageName } from '../../../../common/constants';
-import { getAppLandingUrl } from '../../../common/components/link_to/redirect_to_overview';
-
-const mockNavigateToApp = jest.fn();
-jest.mock('../../../common/lib/kibana', () => {
- const original = jest.requireActual('../../../common/lib/kibana');
-
- return {
- ...original,
- useKibana: () => ({
- services: {
- ...original.useKibana().services,
- application: {
- ...original.useKibana().services.application,
- navigateToApp: mockNavigateToApp,
- },
- },
- }),
- };
-});
-
-describe('Redirect to landing page', () => {
- it('render with correct actions ', () => {
- shallow();
- expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx
deleted file mode 100644
index 91395aa21486f..0000000000000
--- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 { useKibana } from '../../../common/lib/kibana';
-import { APP_UI_ID, SecurityPageName } from '../../../../common/constants';
-import { getAppLandingUrl } from '../../../common/components/link_to/redirect_to_overview';
-
-const OverviewEmptyComponent: React.FC = () => {
- const { navigateToApp } = useKibana().services.application;
-
- navigateToApp(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
- return null;
-};
-
-OverviewEmptyComponent.displayName = 'OverviewEmptyComponent';
-
-export const OverviewEmpty = React.memo(OverviewEmptyComponent);
diff --git a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx
index e29ea1e923a63..f3dc4d400c9c2 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx
@@ -11,7 +11,6 @@ import { SiemSearchBar } from '../../common/components/search_bar';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
// import { useGlobalTime } from '../../common/containers/use_global_time';
-import { OverviewEmpty } from '../components/overview_empty';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../app/types';
import { useSourcererDataView } from '../../common/containers/sourcerer';
@@ -20,6 +19,7 @@ import { HeaderPage } from '../../common/components/header_page';
import { useShallowEqualSelector } from '../../common/hooks/use_selector';
import { DETECTION_RESPONSE_TITLE, UPDATED, UPDATING } from './translations';
import { inputsSelectors } from '../../common/store/selectors';
+import { LandingPageComponent } from '../../common/components/landing_page';
const DetectionResponseComponent = () => {
const getGlobalQuery = useMemo(() => inputsSelectors.globalQuery(), []);
@@ -90,7 +90,7 @@ const DetectionResponseComponent = () => {
>
) : (
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/overview/pages/landing.tsx b/x-pack/plugins/security_solution/public/overview/pages/landing.tsx
index 0554f1f51c28a..0b9760d2a8db4 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/landing.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/landing.tsx
@@ -8,15 +8,12 @@
import React, { memo } from 'react';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../../common/constants';
-import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
-import { LandingCards } from '../components/landing_cards';
+import { LandingPageComponent } from '../../common/components/landing_page';
export const LandingPage = memo(() => {
return (
<>
-
-
-
+
>
);
diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
index e5be86a1c9f91..cd941e26e20a6 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx
@@ -27,9 +27,8 @@ import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experime
import { initialUserPrivilegesState } from '../../common/components/user_privileges/user_privileges_context';
import { EndpointPrivileges } from '../../../common/endpoint/types';
import { useHostRiskScore } from '../../risk_score/containers';
-import { APP_UI_ID, SecurityPageName } from '../../../common/constants';
-import { getAppLandingUrl } from '../../common/components/link_to/redirect_to_overview';
import { mockCasesContract } from '../../../../cases/public/mocks';
+import { LandingPageComponent } from '../../common/components/landing_page';
const mockNavigateToApp = jest.fn();
jest.mock('../../common/lib/kibana', () => {
@@ -303,8 +302,8 @@ describe('Overview', () => {
mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false));
});
- it('renders the Setup Instructions text', () => {
- mount(
+ it('renders getting started page', () => {
+ const wrapper = mount(
@@ -312,10 +311,7 @@ describe('Overview', () => {
);
- expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
+ expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
});
});
});
diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
index ca95f41e0ea12..3f3d37cd3abae 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx
@@ -17,7 +17,6 @@ import { useFetchIndex } from '../../common/containers/source';
import { EventsByDataset } from '../components/events_by_dataset';
import { EventCounts } from '../components/event_counts';
-import { OverviewEmpty } from '../components/overview_empty';
import { StatefulSidebar } from '../components/sidebar';
import { SignalsByCategory } from '../components/signals_by_category';
import { inputsSelectors } from '../../common/store';
@@ -34,6 +33,7 @@ import { useUserPrivileges } from '../../common/components/user_privileges';
import { RiskyHostLinks } from '../components/overview_risky_host_links';
import { useAlertsPrivileges } from '../../detections/containers/detection_engine/alerts/use_alerts_privileges';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
+import { LandingPageComponent } from '../../common/components/landing_page';
const OverviewComponent = () => {
const getGlobalFiltersQuerySelector = useMemo(
@@ -173,7 +173,7 @@ const OverviewComponent = () => {
>
) : (
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
index 1330795841653..242767eac2432 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
@@ -22,12 +22,12 @@ import { useKibana } from '../../../../common/lib/kibana';
import { convertToBuildEsQuery } from '../../../../common/lib/keury';
import { inputsSelectors } from '../../../../common/store';
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
-import { OverviewEmpty } from '../../../../overview/components/overview_empty';
import { getEsQueryConfig } from '../../../../../../../../src/plugins/data/common';
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
import { useNetworkDetails } from '../../../../network/containers/details';
import { networkModel } from '../../../../network/store';
import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data';
+import { LandingCards } from '../../../../common/components/landing_cards';
interface ExpandableNetworkProps {
expandedNetwork: { ip: string; flowTarget: FlowTarget };
@@ -141,6 +141,6 @@ export const ExpandableNetworkDetails = ({
narrowDateRange={narrowDateRange}
/>
) : (
-
+
);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
index 6151316cc303d..bf402eb56c291 100644
--- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx
@@ -15,7 +15,6 @@ import { HeaderPage } from '../../common/components/header_page';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { useKibana } from '../../common/lib/kibana';
import { SpyRoute } from '../../common/utils/route/spy_routes';
-import { OverviewEmpty } from '../../overview/components/overview_empty';
import { StatefulOpenTimeline } from '../components/open_timeline';
import { NEW_TEMPLATE_TIMELINE } from '../components/timeline/properties/translations';
import { NewTemplateTimeline } from '../components/timeline/properties/new_template_timeline';
@@ -23,6 +22,7 @@ import { NewTimeline } from '../components/timeline/properties/helpers';
import * as i18n from './translations';
import { SecurityPageName } from '../../app/types';
import { useSourcererDataView } from '../../common/containers/sourcerer';
+import { LandingPageComponent } from '../../common/components/landing_page';
const TimelinesContainer = styled.div`
width: 100%;
@@ -92,9 +92,7 @@ export const TimelinesPageComponent: React.FC = () => {
>
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx
index 36ace6a6b4543..3dfe67de92c81 100644
--- a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx
@@ -26,7 +26,6 @@ import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions
import { SpyRoute } from '../../../common/utils/route/spy_routes';
import { getEsQueryConfig } from '../../../../../../../src/plugins/data/common';
-import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { UsersDetailsTabs } from './details_tabs';
import { navTabsUsersDetails } from './nav_tabs';
import { UsersDetailsProps } from './types';
@@ -52,6 +51,7 @@ import { getCriteriaFromUsersType } from '../../../common/components/ml/criteria
import { UsersType } from '../../store/model';
import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions';
import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities';
+import { LandingPageComponent } from '../../../common/components/landing_page';
const QUERY_ID = 'UsersDetailsQueryId';
const UsersDetailsComponent: React.FC = ({
@@ -194,11 +194,7 @@ const UsersDetailsComponent: React.FC = ({
>
) : (
-
-
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx
index 6acd2ddf32a3c..ae5b485142f9c 100644
--- a/x-pack/plugins/security_solution/public/users/pages/users.tsx
+++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx
@@ -29,7 +29,6 @@ import { setAbsoluteRangeDatePicker } from '../../common/store/inputs/actions';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { getEsQueryConfig } from '../../../../../../src/plugins/data/common';
-import { OverviewEmpty } from '../../overview/components/overview_empty';
import { UsersTabs } from './users_tabs';
import { navTabsUsers } from './nav_tabs';
import * as i18n from './translations';
@@ -49,6 +48,7 @@ import { UsersTableType } from '../store/model';
import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions';
import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
+import { LandingPageComponent } from '../../common/components/landing_page';
const ID = 'UsersQueryId';
@@ -220,11 +220,7 @@ const UsersComponent = () => {
) : (
-
-
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/users/pages/users_tabs.test.tsx b/x-pack/plugins/security_solution/public/users/pages/users_tabs.test.tsx
index e3807f359a0ff..c3ffdc646966f 100644
--- a/x-pack/plugins/security_solution/public/users/pages/users_tabs.test.tsx
+++ b/x-pack/plugins/security_solution/public/users/pages/users_tabs.test.tsx
@@ -15,8 +15,7 @@ import { SecuritySolutionTabNavigation } from '../../common/components/navigatio
import { Users } from './users';
import { useSourcererDataView } from '../../common/containers/sourcerer';
import { mockCasesContext } from '../../../../cases/public/mocks/mock_cases_context';
-import { APP_UI_ID, SecurityPageName } from '../../../common/constants';
-import { getAppLandingUrl } from '../../common/components/link_to/redirect_to_overview';
+import { LandingPageComponent } from '../../common/components/landing_page';
jest.mock('../../common/containers/sourcerer');
jest.mock('../../common/components/search_bar', () => ({
@@ -73,22 +72,20 @@ const mockHistory = {
};
const mockUseSourcererDataView = useSourcererDataView as jest.Mock;
describe('Users - rendering', () => {
- test('it renders the Setup Instructions text when no index is available', async () => {
+ test('it renders getting started page when no index is available', async () => {
mockUseSourcererDataView.mockReturnValue({
indicesExist: false,
});
- mount(
+ const wrapper = mount(
);
- expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
- deepLinkId: SecurityPageName.landing,
- path: getAppLandingUrl(),
- });
+
+ expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
});
test('it should render tab navigation', async () => {