diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts new file mode 100644 index 00000000000000..ff7aa7581fc4bb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { SecurityPageName } from '../../../app/types'; +import { NavLinkItem } from '../../links/types'; +import { TestProviders } from '../../mock'; +import { useAppNavLinks, useAppRootNavLink } from './nav_links'; + +const mockNavLinks = [ + { + description: 'description', + id: SecurityPageName.administration, + links: [ + { + description: 'description 2', + id: SecurityPageName.endpoints, + links: [], + path: '/path_2', + title: 'title 2', + }, + ], + path: '/path', + title: 'title', + }, +]; + +jest.mock('../../links', () => ({ + getNavLinkItems: () => mockNavLinks, +})); + +const renderUseAppNavLinks = () => + renderHook<{}, NavLinkItem[]>(() => useAppNavLinks(), { wrapper: TestProviders }); + +const renderUseAppRootNavLink = (id: SecurityPageName) => + renderHook<{ id: SecurityPageName }, NavLinkItem | undefined>(() => useAppRootNavLink(id), { + wrapper: TestProviders, + }); + +describe('useAppNavLinks', () => { + it('should return all nav links', () => { + const { result } = renderUseAppNavLinks(); + expect(result.current).toEqual(mockNavLinks); + }); + + it('should return a root nav links', () => { + const { result } = renderUseAppRootNavLink(SecurityPageName.administration); + expect(result.current).toEqual(mockNavLinks[0]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts new file mode 100644 index 00000000000000..efdf72a1f7926b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts @@ -0,0 +1,25 @@ +/* + * 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 { useKibana } from '../../lib/kibana'; +import { useEnableExperimental } from '../../hooks/use_experimental_features'; +import { useLicense } from '../../hooks/use_license'; +import { getNavLinkItems } from '../../links'; +import type { SecurityPageName } from '../../../app/types'; +import type { NavLinkItem } from '../../links/types'; + +export const useAppNavLinks = (): NavLinkItem[] => { + const license = useLicense(); + const enableExperimental = useEnableExperimental(); + const capabilities = useKibana().services.application.capabilities; + + return getNavLinkItems({ enableExperimental, license, capabilities }); +}; + +export const useAppRootNavLink = (linkId: SecurityPageName): NavLinkItem | undefined => { + return useAppNavLinks().find(({ id }) => id === linkId); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index bc20a98eae1e87..91edd1feea2dad 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -76,3 +76,10 @@ export type GetUrlForApp = ( ) => string; export type NavigateToUrl = (url: string) => void; + +export interface NavigationCategory { + label: string; + linkIds: readonly SecurityPageName[]; +} + +export type NavigationCategories = Readonly; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000000..cadb9057ccbccf --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap @@ -0,0 +1,170 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`useSecuritySolutionNavigation should create navigation config 1`] = ` +Object { + "icon": "logoSecurity", + "items": Array [ + Object { + "id": "main", + "items": Array [ + 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", + "disabled": false, + "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)))", + "id": "get_started", + "isSelected": false, + "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": "", + }, + Object { + "id": "detect", + "items": Array [ + Object { + "data-href": "securitySolutionUI/alerts?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-alerts", + "disabled": false, + "href": "securitySolutionUI/alerts?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": "alerts", + "isSelected": false, + "name": "Alerts", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/rules?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-rules", + "disabled": false, + "href": "securitySolutionUI/rules?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": "rules", + "isSelected": false, + "name": "Rules", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/exceptions?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-exceptions", + "disabled": false, + "href": "securitySolutionUI/exceptions?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": "exceptions", + "isSelected": false, + "name": "Exception lists", + "onClick": [Function], + }, + ], + "name": "Detect", + }, + Object { + "id": "explore", + "items": Array [ + Object { + "data-href": "securitySolutionUI/hosts?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-hosts", + "disabled": false, + "href": "securitySolutionUI/hosts?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": "hosts", + "isSelected": true, + "name": "Hosts", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/network?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-network", + "disabled": false, + "href": "securitySolutionUI/network?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": "network", + "isSelected": false, + "name": "Network", + "onClick": [Function], + }, + ], + "name": "Explore", + }, + Object { + "id": "investigate", + "items": Array [ + Object { + "data-href": "securitySolutionUI/timelines?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-timelines", + "disabled": false, + "href": "securitySolutionUI/timelines?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": "timelines", + "isSelected": false, + "name": "Timelines", + "onClick": [Function], + }, + ], + "name": "Investigate", + }, + Object { + "id": "manage", + "items": Array [ + Object { + "data-href": "securitySolutionUI/endpoints", + "data-test-subj": "navigation-endpoints", + "disabled": false, + "href": "securitySolutionUI/endpoints", + "id": "endpoints", + "isSelected": false, + "name": "Endpoints", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/trusted_apps", + "data-test-subj": "navigation-trusted_apps", + "disabled": false, + "href": "securitySolutionUI/trusted_apps", + "id": "trusted_apps", + "isSelected": false, + "name": "Trusted applications", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/event_filters", + "data-test-subj": "navigation-event_filters", + "disabled": false, + "href": "securitySolutionUI/event_filters", + "id": "event_filters", + "isSelected": false, + "name": "Event filters", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/host_isolation_exceptions", + "data-test-subj": "navigation-host_isolation_exceptions", + "disabled": false, + "href": "securitySolutionUI/host_isolation_exceptions", + "id": "host_isolation_exceptions", + "isSelected": false, + "name": "Host isolation exceptions", + "onClick": [Function], + }, + Object { + "data-href": "securitySolutionUI/blocklist", + "data-test-subj": "navigation-blocklist", + "disabled": false, + "href": "securitySolutionUI/blocklist", + "id": "blocklist", + "isSelected": false, + "name": "Blocklist", + "onClick": [Function], + }, + ], + "name": "Manage", + }, + ], + "name": "Security", +} +`; 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 c515f43ee181da..bba345c325d0f6 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 @@ -108,174 +108,7 @@ describe('useSecuritySolutionNavigation', () => { { wrapper: TestProviders } ); - expect(result.current).toMatchInlineSnapshot(` - Object { - "icon": "logoSecurity", - "items": Array [ - Object { - "id": "main", - "items": Array [ - 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", - "disabled": false, - "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)))", - "id": "get_started", - "isSelected": false, - "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": "", - }, - Object { - "id": "detect", - "items": Array [ - Object { - "data-href": "securitySolutionUI/alerts?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-alerts", - "disabled": false, - "href": "securitySolutionUI/alerts?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": "alerts", - "isSelected": false, - "name": "Alerts", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/rules?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-rules", - "disabled": false, - "href": "securitySolutionUI/rules?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": "rules", - "isSelected": false, - "name": "Rules", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/exceptions?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-exceptions", - "disabled": false, - "href": "securitySolutionUI/exceptions?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": "exceptions", - "isSelected": false, - "name": "Exception lists", - "onClick": [Function], - }, - ], - "name": "Detect", - }, - Object { - "id": "explore", - "items": Array [ - Object { - "data-href": "securitySolutionUI/hosts?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-hosts", - "disabled": false, - "href": "securitySolutionUI/hosts?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": "hosts", - "isSelected": true, - "name": "Hosts", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/network?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-network", - "disabled": false, - "href": "securitySolutionUI/network?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": "network", - "isSelected": false, - "name": "Network", - "onClick": [Function], - }, - ], - "name": "Explore", - }, - Object { - "id": "investigate", - "items": Array [ - Object { - "data-href": "securitySolutionUI/timelines?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-timelines", - "disabled": false, - "href": "securitySolutionUI/timelines?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": "timelines", - "isSelected": false, - "name": "Timelines", - "onClick": [Function], - }, - ], - "name": "Investigate", - }, - Object { - "id": "manage", - "items": Array [ - Object { - "data-href": "securitySolutionUI/endpoints", - "data-test-subj": "navigation-endpoints", - "disabled": false, - "href": "securitySolutionUI/endpoints", - "id": "endpoints", - "isSelected": false, - "name": "Endpoints", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/trusted_apps", - "data-test-subj": "navigation-trusted_apps", - "disabled": false, - "href": "securitySolutionUI/trusted_apps", - "id": "trusted_apps", - "isSelected": false, - "name": "Trusted applications", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/event_filters", - "data-test-subj": "navigation-event_filters", - "disabled": false, - "href": "securitySolutionUI/event_filters", - "id": "event_filters", - "isSelected": false, - "name": "Event filters", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/host_isolation_exceptions", - "data-test-subj": "navigation-host_isolation_exceptions", - "disabled": false, - "href": "securitySolutionUI/host_isolation_exceptions", - "id": "host_isolation_exceptions", - "isSelected": false, - "name": "Host isolation exceptions", - "onClick": [Function], - }, - Object { - "data-href": "securitySolutionUI/blocklist", - "data-test-subj": "navigation-blocklist", - "disabled": false, - "href": "securitySolutionUI/blocklist", - "id": "blocklist", - "isSelected": false, - "name": "Blocklist", - "onClick": [Function], - }, - ], - "name": "Manage", - }, - ], - "name": "Security", - } - `); + expect(result.current).toMatchSnapshot(); }); // TODO: Steph/users remove when no longer experimental diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_experimental_features.ts b/x-pack/plugins/security_solution/public/common/hooks/use_experimental_features.ts index 3132ae70381a29..1cc2506ec39968 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_experimental_features.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_experimental_features.ts @@ -14,14 +14,16 @@ import { const allowedExperimentalValues = getExperimentalAllowedValues(); -export const useIsExperimentalFeatureEnabled = (feature: keyof ExperimentalFeatures): boolean => - useSelector(({ app: { enableExperimental } }: State) => { - if (!enableExperimental || !(feature in enableExperimental)) { - throw new Error( - `Invalid enable value ${feature}. Allowed values are: ${allowedExperimentalValues.join( - ', ' - )}` - ); - } - return enableExperimental[feature]; - }); +export const useIsExperimentalFeatureEnabled = (feature: keyof ExperimentalFeatures): boolean => { + const enableExperimental = useEnableExperimental(); + + if (!enableExperimental || !(feature in enableExperimental)) { + throw new Error( + `Invalid enable value ${feature}. Allowed values are: ${allowedExperimentalValues.join(', ')}` + ); + } + return enableExperimental[feature]; +}; + +export const useEnableExperimental = (): ExperimentalFeatures => + useSelector(({ app: { enableExperimental } }: State) => enableExperimental); diff --git a/x-pack/plugins/security_solution/public/common/images/detection_response_page.png b/x-pack/plugins/security_solution/public/common/images/detection_response_page.png new file mode 100644 index 00000000000000..630cd555598432 Binary files /dev/null and b/x-pack/plugins/security_solution/public/common/images/detection_response_page.png differ diff --git a/x-pack/plugins/security_solution/public/common/links/links.test.ts b/x-pack/plugins/security_solution/public/common/links/links.test.ts index d8f6711cfc6295..b86b05f48607df 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.test.ts +++ b/x-pack/plugins/security_solution/public/common/links/links.test.ts @@ -94,6 +94,27 @@ const mockLicense = { isAtLeast: licensePremiumMock, } as unknown as LicenseService; +const threatHuntingLinkInfo = { + features: ['siem.show'], + globalNavEnabled: false, + globalSearchKeywords: ['Threat hunting'], + id: 'threat-hunting', + path: '/threat_hunting', + title: 'Threat Hunting', +}; + +const hostsLinkInfo = { + globalNavEnabled: true, + globalNavOrder: 9002, + globalSearchEnabled: true, + globalSearchKeywords: ['Hosts'], + id: 'hosts', + path: '/hosts', + title: 'Hosts', + landingImage: 'test-file-stub', + description: 'A comprehensive overview of all hosts and host-related security events.', +}; + describe('security app link helpers', () => { beforeEach(() => { mockLicense.isAtLeast = licensePremiumMock; @@ -344,46 +365,13 @@ describe('security app link helpers', () => { describe('getAncestorLinksInfo', () => { it('finds flattened links for hosts', () => { const hierarchy = getAncestorLinksInfo(SecurityPageName.hosts); - expect(hierarchy).toEqual([ - { - features: ['siem.show'], - globalNavEnabled: false, - globalSearchKeywords: ['Threat hunting'], - id: 'threat-hunting', - path: '/threat_hunting', - title: 'Threat Hunting', - }, - { - globalNavEnabled: true, - globalNavOrder: 9002, - globalSearchEnabled: true, - globalSearchKeywords: ['Hosts'], - id: 'hosts', - path: '/hosts', - title: 'Hosts', - }, - ]); + expect(hierarchy).toEqual([threatHuntingLinkInfo, hostsLinkInfo]); }); it('finds flattened links for uncommonProcesses', () => { const hierarchy = getAncestorLinksInfo(SecurityPageName.uncommonProcesses); expect(hierarchy).toEqual([ - { - features: ['siem.show'], - globalNavEnabled: false, - globalSearchKeywords: ['Threat hunting'], - id: 'threat-hunting', - path: '/threat_hunting', - title: 'Threat Hunting', - }, - { - globalNavEnabled: true, - globalNavOrder: 9002, - globalSearchEnabled: true, - globalSearchKeywords: ['Hosts'], - id: 'hosts', - path: '/hosts', - title: 'Hosts', - }, + threatHuntingLinkInfo, + hostsLinkInfo, { id: 'uncommon_processes', path: '/hosts/uncommonProcesses', @@ -407,15 +395,7 @@ describe('security app link helpers', () => { describe('getLinkInfo', () => { it('gets information for an individual link', () => { const linkInfo = getLinkInfo(SecurityPageName.hosts); - expect(linkInfo).toEqual({ - globalNavEnabled: true, - globalNavOrder: 9002, - globalSearchEnabled: true, - globalSearchKeywords: ['Hosts'], - id: 'hosts', - path: '/hosts', - title: 'Hosts', - }); + expect(linkInfo).toEqual(hostsLinkInfo); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/links/links.ts b/x-pack/plugins/security_solution/public/common/links/links.ts index 290a1f3fbd8208..af9357a122a1eb 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.ts +++ b/x-pack/plugins/security_solution/public/common/links/links.ts @@ -32,8 +32,6 @@ const createDeepLink = (link: LinkItem, linkProps?: UserPermissions): AppDeepLin }), } : {}), - ...(link.icon != null ? { euiIconType: link.icon } : {}), - ...(link.image != null ? { icon: link.image } : {}), ...(link.globalSearchKeywords != null ? { keywords: link.globalSearchKeywords } : {}), ...(link.globalNavEnabled != null ? { navLinkStatus: link.globalNavEnabled ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden } @@ -47,8 +45,8 @@ const createNavLinkItem = (link: LinkItem, linkProps?: UserPermissions): NavLink path: link.path, title: link.title, ...(link.description != null ? { description: link.description } : {}), - ...(link.icon != null ? { icon: link.icon } : {}), - ...(link.image != null ? { image: link.image } : {}), + ...(link.landingIcon != null ? { icon: link.landingIcon } : {}), + ...(link.landingImage != null ? { image: link.landingImage } : {}), ...(link.links && link.links.length ? { links: reduceLinks({ diff --git a/x-pack/plugins/security_solution/public/common/links/types.ts b/x-pack/plugins/security_solution/public/common/links/types.ts index eea348b3df7377..320c38d1d229b2 100644 --- a/x-pack/plugins/security_solution/public/common/links/types.ts +++ b/x-pack/plugins/security_solution/public/common/links/types.ts @@ -7,6 +7,7 @@ import { Capabilities } from '@kbn/core/types'; import { LicenseType } from '@kbn/licensing-plugin/common/types'; +import { IconType } from '@elastic/eui'; import { LicenseService } from '../../../common/license'; import { ExperimentalFeatures } from '../../../common/experimental_features'; import { CASES_FEATURE_ID, SecurityPageName, SERVER_APP_ID } from '../../../common/constants'; @@ -41,9 +42,17 @@ export interface LinkItem { globalSearchEnabled?: boolean; globalSearchKeywords?: string[]; hideWhenExperimentalKey?: keyof ExperimentalFeatures; - icon?: string; id: SecurityPageName; - image?: string; + /** + * Icon that is displayed on menu navigation landing page. + * Only required for pages that are displayed inside a landing page. + */ + landingIcon?: IconType; + /** + * Image that is displayed on menu navigation landing page. + * Only required for pages that are displayed inside a landing page. + */ + landingImage?: string; isBeta?: boolean; licenseType?: LicenseType; links?: LinkItem[]; @@ -54,7 +63,7 @@ export interface LinkItem { export interface NavLinkItem { description?: string; - icon?: string; + icon?: IconType; id: SecurityPageName; links?: NavLinkItem[]; image?: string; diff --git a/x-pack/plugins/security_solution/public/hosts/links.ts b/x-pack/plugins/security_solution/public/hosts/links.ts index 0617604627e3e2..d1bc26c5fb3f2f 100644 --- a/x-pack/plugins/security_solution/public/hosts/links.ts +++ b/x-pack/plugins/security_solution/public/hosts/links.ts @@ -8,10 +8,15 @@ import { i18n } from '@kbn/i18n'; import { HOSTS_PATH, SecurityPageName } from '../../common/constants'; import { HOSTS } from '../app/translations'; import { LinkItem } from '../common/links/types'; +import hostsPageImg from '../common/images/hosts_page.png'; export const links: LinkItem = { id: SecurityPageName.hosts, title: HOSTS, + landingImage: hostsPageImg, + description: i18n.translate('xpack.securitySolution.landing.threatHunting.hostsDescription', { + defaultMessage: 'A comprehensive overview of all hosts and host-related security events.', + }), path: HOSTS_PATH, globalNavEnabled: true, globalSearchKeywords: [ diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.test.tsx index c2ab11ceead79a..81b72527500adf 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.test.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.test.tsx @@ -8,14 +8,16 @@ import { render } from '@testing-library/react'; import React from 'react'; import { SecurityPageName } from '../../app/types'; +import { NavLinkItem } from '../../common/links/types'; import { TestProviders } from '../../common/mock'; -import { LandingLinksIcons, NavItem } from './landing_links_icons'; +import { LandingLinksIcons } from './landing_links_icons'; -const DEFAULT_NAV_ITEM: NavItem = { +const DEFAULT_NAV_ITEM: NavLinkItem = { id: SecurityPageName.overview, - label: 'TEST LABEL', + title: 'TEST LABEL', description: 'TEST DESCRIPTION', icon: 'myTestIcon', + path: '', }; const mockNavigateTo = jest.fn(); @@ -40,28 +42,28 @@ jest.mock('../../common/components/link_to', () => { describe('LandingLinksIcons', () => { it('renders', () => { - const label = 'test label'; + const title = 'test label'; const { queryByText } = render( - + ); - expect(queryByText(label)).toBeInTheDocument(); + expect(queryByText(title)).toBeInTheDocument(); }); it('renders navigation link', () => { const id = SecurityPageName.administration; - const label = 'myTestLable'; + const title = 'myTestLable'; const { getByText } = render( - + ); - getByText(label).click(); + getByText(title).click(); expect(mockNavigateTo).toHaveBeenCalledWith({ url: '/administration' }); }); diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx index 82a0d2148f6835..04a3e20b1f1789 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx @@ -4,33 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiText, - EuiTitle, - IconType, -} from '@elastic/eui'; +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTitle } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { SecurityPageName } from '../../app/types'; + import { SecuritySolutionLinkAnchor, withSecuritySolutionLink, } from '../../common/components/links'; +import { NavLinkItem } from '../../common/links/types'; interface LandingLinksImagesProps { - items: NavItem[]; -} - -export interface NavItem { - id: SecurityPageName; - label: string; - icon: IconType; - description: string; - path?: string; + items: NavLinkItem[]; } const Link = styled.a` @@ -50,7 +35,7 @@ const StyledEuiTitle = styled(EuiTitle)` export const LandingLinksIcons: React.FC = ({ items }) => ( - {items.map(({ label, description, path, id, icon }) => ( + {items.map(({ title, description, id, icon }) => ( = ({ items }) responsive={false} > - - - -

{label}

+ +

{title}

diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.test.tsx index 479de5e13f4326..c44374852f29bf 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.test.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.test.tsx @@ -8,14 +8,16 @@ import { render } from '@testing-library/react'; import React from 'react'; import { SecurityPageName } from '../../app/types'; +import { NavLinkItem } from '../../common/links/types'; import { TestProviders } from '../../common/mock'; -import { LandingLinksImages, NavItem } from './landing_links_images'; +import { LandingLinksImages } from './landing_links_images'; -const DEFAULT_NAV_ITEM: NavItem = { +const DEFAULT_NAV_ITEM: NavLinkItem = { id: SecurityPageName.overview, - label: 'TEST LABEL', + title: 'TEST LABEL', description: 'TEST DESCRIPTION', image: 'TEST_IMAGE.png', + path: '', }; jest.mock('../../common/lib/kibana/kibana_react', () => { @@ -32,24 +34,24 @@ jest.mock('../../common/lib/kibana/kibana_react', () => { describe('LandingLinksImages', () => { it('renders', () => { - const label = 'test label'; + const title = 'test label'; const { queryByText } = render( - + ); - expect(queryByText(label)).toBeInTheDocument(); + expect(queryByText(title)).toBeInTheDocument(); }); it('renders image', () => { const image = 'test_image.jpeg'; - const label = 'TEST_LABEL'; + const title = 'TEST_LABEL'; const { getByTestId } = render( - + ); diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx index b6a16da8cdc82e..22bcc0f1aa2516 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx @@ -7,19 +7,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { SecurityPageName } from '../../app/types'; import { withSecuritySolutionLink } from '../../common/components/links'; +import { NavLinkItem } from '../../common/links/types'; interface LandingLinksImagesProps { - items: NavItem[]; -} - -export interface NavItem { - id: SecurityPageName; - label: string; - image: string; - description: string; - path?: string; + items: NavLinkItem[]; } const PrimaryEuiTitle = styled(EuiTitle)` @@ -47,24 +39,26 @@ const Content = styled(EuiFlexItem)` export const LandingLinksImages: React.FC = ({ items }) => ( - {items.map(({ label, description, path, image, id }) => ( + {items.map(({ title, description, image, id }) => ( - + {/* Empty onClick is to force hover style on `EuiPanel` */} {}}> - + {image && ( + + )} -

{label}

+

{title}

{description} diff --git a/x-pack/plugins/security_solution/public/landing_pages/constants.ts b/x-pack/plugins/security_solution/public/landing_pages/constants.ts new file mode 100644 index 00000000000000..a6b72a5e7db4f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/landing_pages/constants.ts @@ -0,0 +1,36 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { SecurityPageName } from '../app/types'; + +export interface LandingNavGroup { + label: string; + itemIds: SecurityPageName[]; +} + +export const MANAGE_NAVIGATION_CATEGORIES: LandingNavGroup[] = [ + { + label: i18n.translate('xpack.securitySolution.landing.siemTitle', { + defaultMessage: 'SIEM', + }), + itemIds: [SecurityPageName.rules, SecurityPageName.exceptions], + }, + { + label: i18n.translate('xpack.securitySolution.landing.endpointsTitle', { + defaultMessage: 'ENDPOINTS', + }), + itemIds: [ + SecurityPageName.endpoints, + SecurityPageName.policies, + SecurityPageName.trustedApps, + SecurityPageName.eventFilters, + SecurityPageName.blocklist, + SecurityPageName.hostIsolationExceptions, + ], + }, +]; diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx index 8c49fda169ad37..1d46aa6706a266 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/dashboards.tsx @@ -5,31 +5,22 @@ * 2.0. */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { SecurityPageName } from '../../app/types'; import { HeaderPage } from '../../common/components/header_page'; +import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { LandingLinksImages, NavItem } from '../components/landing_links_images'; +import { LandingLinksImages } from '../components/landing_links_images'; import { DASHBOARDS_PAGE_TITLE } from './translations'; -import overviewPageImg from '../../common/images/overview_page.png'; -import { OVERVIEW } from '../../app/translations'; -const items: NavItem[] = [ - { - id: SecurityPageName.overview, - label: OVERVIEW, - description: i18n.translate('xpack.securitySolution.landing.dashboards.overviewDescription', { - defaultMessage: 'What is going in your secuity environment', - }), - image: overviewPageImg, - }, -]; +export const DashboardsLandingPage = () => { + const dashboardLinks = useAppRootNavLink(SecurityPageName.dashboardsLanding)?.links ?? []; -export const DashboardsLandingPage = () => ( - - - - - -); + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx index efb1bcf35c39ed..1955d56c0a151a 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx @@ -9,43 +9,53 @@ import { render } from '@testing-library/react'; import React from 'react'; import { SecurityPageName } from '../../app/types'; import { TestProviders } from '../../common/mock'; -import { LandingCategories, NavConfigType } from './manage'; +import { LandingCategories } from './manage'; +import { NavLinkItem } from '../../common/links/types'; const RULES_ITEM_LABEL = 'elastic rules!'; const EXCEPTIONS_ITEM_LABEL = 'exceptional!'; -const testConfig: NavConfigType = { - categories: [ - { - label: 'first tests category', - itemIds: [SecurityPageName.rules], - }, - { - label: 'second tests category', - itemIds: [SecurityPageName.exceptions], - }, - ], - items: [ +const mockAppManageLink: NavLinkItem = { + id: SecurityPageName.administration, + path: '', + title: 'admin', + links: [ { id: SecurityPageName.rules, - label: RULES_ITEM_LABEL, + title: RULES_ITEM_LABEL, description: '', icon: 'testIcon1', + path: '', }, { id: SecurityPageName.exceptions, - label: EXCEPTIONS_ITEM_LABEL, + title: EXCEPTIONS_ITEM_LABEL, description: '', icon: 'testIcon2', + path: '', }, ], }; +jest.mock('../../common/components/navigation/nav_links', () => ({ + useAppRootNavLink: jest.fn(() => mockAppManageLink), +})); describe('LandingCategories', () => { it('renders items', () => { const { queryByText } = render( - + ); @@ -57,15 +67,12 @@ describe('LandingCategories', () => { const { queryAllByTestId } = render( ); diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx index da4d25f6213058..f0e6094d5113fe 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx @@ -5,140 +5,23 @@ * 2.0. */ import { EuiHorizontalRule, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { compact } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; -import { - BLOCKLIST, - ENDPOINTS, - EVENT_FILTERS, - EXCEPTIONS, - TRUSTED_APPLICATIONS, -} from '../../app/translations'; + import { SecurityPageName } from '../../app/types'; import { HeaderPage } from '../../common/components/header_page'; +import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; +import { NavigationCategories } from '../../common/components/navigation/types'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { LandingLinksIcons, NavItem } from '../components/landing_links_icons'; -import { IconBlocklist } from '../icons/blocklist'; -import { IconEndpoints } from '../icons/endpoints'; -import { IconEndpointPolicies } from '../icons/endpoint_policies'; -import { IconEventFilters } from '../icons/event_filters'; -import { IconExceptionLists } from '../icons/exception_lists'; -import { IconHostIsolation } from '../icons/host_isolation'; -import { IconSiemRules } from '../icons/siem_rules'; -import { IconTrustedApplications } from '../icons/trusted_applications'; +import { navigationCategories } from '../../management/links'; +import { LandingLinksIcons } from '../components/landing_links_icons'; import { MANAGE_PAGE_TITLE } from './translations'; -// TODO -const FIX_ME_TEMPORARY_DESCRIPTION = 'Description here'; - -export interface NavConfigType { - items: NavItem[]; - categories: Array<{ label: string; itemIds: SecurityPageName[] }>; -} - -const config: NavConfigType = { - categories: [ - { - label: i18n.translate('xpack.securitySolution.landing.threatHunting.siemTitle', { - defaultMessage: 'SIEM', - }), - itemIds: [SecurityPageName.rules, SecurityPageName.exceptions], - }, - { - label: i18n.translate('xpack.securitySolution.landing.threatHunting.endpointsTitle', { - defaultMessage: 'ENDPOINTS', - }), - itemIds: [ - SecurityPageName.endpoints, - SecurityPageName.policies, - SecurityPageName.trustedApps, - SecurityPageName.eventFilters, - SecurityPageName.blocklist, - SecurityPageName.hostIsolationExceptions, - ], - }, - ], - items: [ - { - id: SecurityPageName.rules, - label: i18n.translate('xpack.securitySolution.landing.manage.rulesLabel', { - defaultMessage: 'SIEM rules', - }), - description: FIX_ME_TEMPORARY_DESCRIPTION, - icon: IconSiemRules, - }, - { - id: SecurityPageName.exceptions, - label: EXCEPTIONS, - description: FIX_ME_TEMPORARY_DESCRIPTION, - icon: IconExceptionLists, - }, - { - id: SecurityPageName.endpoints, - label: ENDPOINTS, - description: i18n.translate('xpack.securitySolution.landing.manage.endpointsDescription', { - defaultMessage: 'Hosts running endpoint security', - }), - icon: IconEndpoints, - }, - { - id: SecurityPageName.policies, - label: i18n.translate('xpack.securitySolution.landing.manage.endpointPoliceLabel', { - defaultMessage: 'Endpoint policies', - }), - description: FIX_ME_TEMPORARY_DESCRIPTION, - icon: IconEndpointPolicies, - }, - { - id: SecurityPageName.trustedApps, - label: TRUSTED_APPLICATIONS, - description: i18n.translate( - 'xpack.securitySolution.landing.manage.trustedApplicationsDescription', - { - defaultMessage: - 'Improve performance or alleviate conflicts with other applications running on your hosts', - } - ), - icon: IconTrustedApplications, - }, - { - id: SecurityPageName.eventFilters, - label: EVENT_FILTERS, - description: i18n.translate('xpack.securitySolution.landing.manage.eventFiltersDescription', { - defaultMessage: 'Exclude unwanted applications from running on your hosts', - }), - icon: IconEventFilters, - }, - { - id: SecurityPageName.blocklist, - label: BLOCKLIST, - description: FIX_ME_TEMPORARY_DESCRIPTION, - icon: IconBlocklist, - }, - { - id: SecurityPageName.hostIsolationExceptions, - label: i18n.translate('xpack.securitySolution.landing.manage.hostIsolationLabel', { - defaultMessage: 'Host isolation IP exceptions', - }), - description: i18n.translate( - 'xpack.securitySolution.landing.manage.hostIsolationDescription', - { - defaultMessage: 'Allow isolated hosts to communicate with specific IPs', - } - ), - - icon: IconHostIsolation, - }, - ], -}; - export const ManageLandingPage = () => ( - + ); @@ -148,32 +31,37 @@ const StyledEuiHorizontalRule = styled(EuiHorizontalRule)` margin-bottom: ${({ theme }) => theme.eui.paddingSizes.l}; `; -const getNavItembyId = (navConfig: NavConfigType) => (itemId: string) => - navConfig.items.find(({ id }: NavItem) => id === itemId); +const useGetManageNavLinks = () => { + const manageNavLinks = useAppRootNavLink(SecurityPageName.administration)?.links ?? []; + + const manageLinksById = Object.fromEntries(manageNavLinks.map((link) => [link.id, link])); + return (linkIds: readonly SecurityPageName[]) => linkIds.map((linkId) => manageLinksById[linkId]); +}; -const navItemsFromIds = (itemIds: SecurityPageName[], navConfig: NavConfigType) => - compact(itemIds.map(getNavItembyId(navConfig))); +export const LandingCategories = React.memo( + ({ categories }: { categories: NavigationCategories }) => { + const getManageNavLinks = useGetManageNavLinks(); -export const LandingCategories = React.memo(({ navConfig }: { navConfig: NavConfigType }) => { - return ( - <> - {navConfig.categories.map(({ label, itemIds }, index) => ( -
- {index > 0 && ( - <> - - - - )} - -

{label}

-
- - -
- ))} - - ); -}); + return ( + <> + {categories.map(({ label, linkIds }, index) => ( +
+ {index > 0 && ( + <> + + + + )} + +

{label}

+
+ + +
+ ))} + + ); + } +); LandingCategories.displayName = 'LandingCategories'; diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx index 2a0f4e471a75de..605a1baeedbd6e 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/threat_hunting.tsx @@ -5,51 +5,22 @@ * 2.0. */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { SecurityPageName } from '../../app/types'; import { HeaderPage } from '../../common/components/header_page'; +import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { LandingLinksImages, NavItem } from '../components/landing_links_images'; +import { LandingLinksImages } from '../components/landing_links_images'; import { THREAT_HUNTING_PAGE_TITLE } from './translations'; -import userPageImg from '../../common/images/users_page.png'; -import hostsPageImg from '../../common/images/hosts_page.png'; -import networkPageImg from '../../common/images/network_page.png'; -import { HOSTS, NETWORK, USERS } from '../../app/translations'; -const items: NavItem[] = [ - { - id: SecurityPageName.hosts, - label: HOSTS, - description: i18n.translate('xpack.securitySolution.landing.threatHunting.hostsDescription', { - defaultMessage: - 'Computer or other device that communicates with other hosts on a network. Hosts on a network include clients and servers -- that send or receive data, services or applications.', - }), - image: hostsPageImg, - }, - { - id: SecurityPageName.network, - label: NETWORK, - description: i18n.translate('xpack.securitySolution.landing.threatHunting.networkDescription', { - defaultMessage: - 'The action or process of interacting with others to exchange information and develop professional or social contacts.', - }), - image: networkPageImg, - }, - { - id: SecurityPageName.users, - label: USERS, - description: i18n.translate('xpack.securitySolution.landing.threatHunting.usersDescription', { - defaultMessage: 'Sudo commands dashboard from the Logs System integration.', - }), - image: userPageImg, - }, -]; +export const ThreatHuntingLandingPage = () => { + const threatHuntinglinks = useAppRootNavLink(SecurityPageName.threatHuntingLanding)?.links ?? []; -export const ThreatHuntingLandingPage = () => ( - - - - - -); + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/blocklist.tsx b/x-pack/plugins/security_solution/public/management/icons/blocklist.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/blocklist.tsx rename to x-pack/plugins/security_solution/public/management/icons/blocklist.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/endpoint_policies.tsx b/x-pack/plugins/security_solution/public/management/icons/endpoint_policies.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/endpoint_policies.tsx rename to x-pack/plugins/security_solution/public/management/icons/endpoint_policies.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/endpoints.tsx b/x-pack/plugins/security_solution/public/management/icons/endpoints.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/endpoints.tsx rename to x-pack/plugins/security_solution/public/management/icons/endpoints.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/event_filters.tsx b/x-pack/plugins/security_solution/public/management/icons/event_filters.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/event_filters.tsx rename to x-pack/plugins/security_solution/public/management/icons/event_filters.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/exception_lists.tsx b/x-pack/plugins/security_solution/public/management/icons/exception_lists.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/exception_lists.tsx rename to x-pack/plugins/security_solution/public/management/icons/exception_lists.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/host_isolation.tsx b/x-pack/plugins/security_solution/public/management/icons/host_isolation.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/host_isolation.tsx rename to x-pack/plugins/security_solution/public/management/icons/host_isolation.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/siem_rules.tsx b/x-pack/plugins/security_solution/public/management/icons/siem_rules.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/siem_rules.tsx rename to x-pack/plugins/security_solution/public/management/icons/siem_rules.tsx diff --git a/x-pack/plugins/security_solution/public/landing_pages/icons/trusted_applications.tsx b/x-pack/plugins/security_solution/public/management/icons/trusted_applications.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/landing_pages/icons/trusted_applications.tsx rename to x-pack/plugins/security_solution/public/management/icons/trusted_applications.tsx diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index d941d538c80f7c..54c0b3f0d8dd24 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -29,8 +29,18 @@ import { RULES, TRUSTED_APPLICATIONS, } from '../app/translations'; +import { NavigationCategories } from '../common/components/navigation/types'; import { FEATURE, LinkItem } from '../common/links/types'; +import { IconBlocklist } from './icons/blocklist'; +import { IconEndpoints } from './icons/endpoints'; +import { IconEndpointPolicies } from './icons/endpoint_policies'; +import { IconEventFilters } from './icons/event_filters'; +import { IconExceptionLists } from './icons/exception_lists'; +import { IconHostIsolation } from './icons/host_isolation'; +import { IconSiemRules } from './icons/siem_rules'; +import { IconTrustedApplications } from './icons/trusted_applications'; + export const links: LinkItem = { id: SecurityPageName.administration, title: MANAGE, @@ -47,6 +57,12 @@ export const links: LinkItem = { { id: SecurityPageName.rules, title: RULES, + description: i18n.translate('xpack.securitySolution.appLinks.rulesDescription', { + defaultMessage: + "Create and manage rules to check for suspicious source events, and create alerts when a rule's conditions are met.", + }), + + landingIcon: IconSiemRules, path: RULES_PATH, globalNavEnabled: false, globalSearchKeywords: [ @@ -59,6 +75,10 @@ export const links: LinkItem = { { id: SecurityPageName.exceptions, title: EXCEPTIONS, + description: i18n.translate('xpack.securitySolution.appLinks.exceptionsDescription', { + defaultMessage: 'Create and manage exceptions to prevent the creation of unwanted alerts.', + }), + landingIcon: IconExceptionLists, path: EXCEPTIONS_PATH, globalNavEnabled: false, globalSearchKeywords: [ @@ -70,6 +90,10 @@ export const links: LinkItem = { }, { id: SecurityPageName.endpoints, + description: i18n.translate('xpack.securitySolution.appLinks.endpointsDescription', { + defaultMessage: 'Hosts running endpoint security.', + }), + landingIcon: IconEndpoints, globalNavEnabled: true, title: ENDPOINTS, globalNavOrder: 9006, @@ -79,6 +103,11 @@ export const links: LinkItem = { { id: SecurityPageName.policies, title: POLICIES, + description: i18n.translate('xpack.securitySolution.appLinks.policiesDescription', { + defaultMessage: + 'Use policies to customize endpoint and cloud workload protections and other configurations.', + }), + landingIcon: IconEndpointPolicies, path: POLICIES_PATH, skipUrlState: true, experimentalKey: 'policyListEnabled', @@ -86,26 +115,68 @@ export const links: LinkItem = { { id: SecurityPageName.trustedApps, title: TRUSTED_APPLICATIONS, + description: i18n.translate( + 'xpack.securitySolution.appLinks.trustedApplicationsDescription', + { + defaultMessage: + 'Improve performance or alleviate conflicts with other applications running on your hosts.', + } + ), + landingIcon: IconTrustedApplications, path: TRUSTED_APPS_PATH, skipUrlState: true, }, { id: SecurityPageName.eventFilters, title: EVENT_FILTERS, + description: i18n.translate('xpack.securitySolution.appLinks.eventFiltersDescription', { + defaultMessage: 'Exclude high volume or unwanted events being written into Elasticsearch.', + }), + landingIcon: IconEventFilters, path: EVENT_FILTERS_PATH, skipUrlState: true, }, { id: SecurityPageName.hostIsolationExceptions, title: HOST_ISOLATION_EXCEPTIONS, + description: i18n.translate('xpack.securitySolution.appLinks.hostIsolationDescription', { + defaultMessage: 'Allow isolated hosts to communicate with specific IPs.', + }), + landingIcon: IconHostIsolation, path: HOST_ISOLATION_EXCEPTIONS_PATH, skipUrlState: true, }, { id: SecurityPageName.blocklist, title: BLOCKLIST, + description: i18n.translate('xpack.securitySolution.appLinks.blocklistDescription', { + defaultMessage: 'Exclude unwanted applications from running on your hosts.', + }), + landingIcon: IconBlocklist, path: BLOCKLIST_PATH, skipUrlState: true, }, ], }; + +export const navigationCategories: NavigationCategories = [ + { + label: i18n.translate('xpack.securitySolution.appLinks.category.siem', { + defaultMessage: 'SIEM', + }), + linkIds: [SecurityPageName.rules, SecurityPageName.exceptions], + }, + { + label: i18n.translate('xpack.securitySolution.appLinks.category.endpoints', { + defaultMessage: 'ENDPOINTS', + }), + linkIds: [ + SecurityPageName.endpoints, + SecurityPageName.policies, + SecurityPageName.trustedApps, + SecurityPageName.eventFilters, + SecurityPageName.blocklist, + SecurityPageName.hostIsolationExceptions, + ], + }, +] as const; diff --git a/x-pack/plugins/security_solution/public/network/links.ts b/x-pack/plugins/security_solution/public/network/links.ts index ad209a220eebcb..47194fe3d67a68 100644 --- a/x-pack/plugins/security_solution/public/network/links.ts +++ b/x-pack/plugins/security_solution/public/network/links.ts @@ -9,10 +9,16 @@ import { i18n } from '@kbn/i18n'; import { NETWORK_PATH, SecurityPageName } from '../../common/constants'; import { NETWORK } from '../app/translations'; import { LinkItem } from '../common/links/types'; +import networkPageImg from '../common/images/network_page.png'; export const links: LinkItem = { id: SecurityPageName.network, title: NETWORK, + landingImage: networkPageImg, + description: i18n.translate('xpack.securitySolution.appLinks.network.description', { + defaultMessage: + 'Provides key activity metrics in an interactive map as well as event tables that enable interaction with the Timeline.', + }), path: NETWORK_PATH, globalNavEnabled: true, globalSearchKeywords: [ diff --git a/x-pack/plugins/security_solution/public/overview/links.ts b/x-pack/plugins/security_solution/public/overview/links.ts index 89f75053b3d6f9..d09c23a6cfc62b 100644 --- a/x-pack/plugins/security_solution/public/overview/links.ts +++ b/x-pack/plugins/security_solution/public/overview/links.ts @@ -15,10 +15,16 @@ import { } from '../../common/constants'; import { DASHBOARDS, DETECTION_RESPONSE, GETTING_STARTED, OVERVIEW } from '../app/translations'; import { FEATURE, LinkItem } from '../common/links/types'; +import overviewPageImg from '../common/images/overview_page.png'; +import detectionResponsePageImg from '../common/images/detection_response_page.png'; export const overviewLinks: LinkItem = { id: SecurityPageName.overview, title: OVERVIEW, + landingImage: overviewPageImg, + description: i18n.translate('xpack.securitySolution.appLinks.overviewDescription', { + defaultMessage: 'What is going on in your security environment.', + }), path: OVERVIEW_PATH, globalNavEnabled: true, features: [FEATURE.general], @@ -47,6 +53,11 @@ export const gettingStartedLinks: LinkItem = { export const detectionResponseLinks: LinkItem = { id: SecurityPageName.detectionAndResponse, title: DETECTION_RESPONSE, + landingImage: detectionResponsePageImg, + description: i18n.translate('xpack.securitySolution.appLinks.detectionAndResponseDescription', { + defaultMessage: + "Monitor the impact of application and device performance from the end user's point of view.", + }), path: DETECTION_RESPONSE_PATH, globalNavEnabled: false, experimentalKey: 'detectionResponseEnabled', diff --git a/x-pack/plugins/security_solution/public/users/links.ts b/x-pack/plugins/security_solution/public/users/links.ts index c84b80d6ddb809..a06eaa1b9f566c 100644 --- a/x-pack/plugins/security_solution/public/users/links.ts +++ b/x-pack/plugins/security_solution/public/users/links.ts @@ -9,10 +9,16 @@ import { i18n } from '@kbn/i18n'; import { SecurityPageName, USERS_PATH } from '../../common/constants'; import { USERS } from '../app/translations'; import { LinkItem } from '../common/links/types'; +import userPageImg from '../common/images/users_page.png'; export const links: LinkItem = { id: SecurityPageName.users, title: USERS, + landingImage: userPageImg, + description: i18n.translate('xpack.securitySolution.appLinks.users.description', { + defaultMessage: + 'A comprehensive overview of user data that enables understanding of authentication and user behavior within your environment.', + }), path: USERS_PATH, globalNavEnabled: true, experimentalKey: 'usersEnabled',