mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Serverless] Left navigation (#156600)
## Summary closes: https://github.com/elastic/kibana/issues/156414 Adds the basic navigation to the Security Solution project. - Renders the current navigation hierarchy as in the classic Security. - Uses the basic styles defined by Core (dark sideNav has been dropped). - Reuses the Security SideNav package. - Adds the `setSideNavComponent` API to the Serverless plugin.  ### Run project `yarn serverless-security` ## Next steps - Add the new features needed in the navigation package to align with the new Security IA design. - Update the configuration of the links to display the hierarchy defined by the new Security IA. - Add Serverless specific styles to the sideNav in the ServerlesSecurity plugin. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1abd32cb3a
commit
c4d233d29a
27 changed files with 1103 additions and 9 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -579,7 +579,7 @@ x-pack/plugins/serverless @elastic/appex-sharedux
|
|||
x-pack/plugins/serverless_observability @elastic/appex-sharedux
|
||||
packages/serverless/project_switcher @elastic/appex-sharedux
|
||||
x-pack/plugins/serverless_search @elastic/appex-sharedux
|
||||
x-pack/plugins/serverless_security @elastic/appex-sharedux
|
||||
x-pack/plugins/serverless_security @elastic/security-solution
|
||||
packages/serverless/storybook/config @elastic/appex-sharedux
|
||||
packages/serverless/types @elastic/appex-sharedux
|
||||
test/plugin_functional/plugins/session_notifications @elastic/kibana-core
|
||||
|
@ -789,6 +789,7 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations
|
|||
#CC# /src/plugins/home/server/services/ @elastic/appex-sharedux
|
||||
#CC# /src/plugins/home/ @elastic/appex-sharedux
|
||||
#CC# /x-pack/plugins/reporting/ @elastic/appex-sharedux
|
||||
#CC# /x-pack/plugins/serverless_security/ @elastic/appex-sharedux
|
||||
|
||||
### Observability Plugins
|
||||
|
||||
|
|
23
x-pack/plugins/security_solution/public/mocks.ts
Normal file
23
x-pack/plugins/security_solution/public/mocks.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { BehaviorSubject } from 'rxjs';
|
||||
import type { NavigationLink } from './common/links/types';
|
||||
|
||||
const setupMock = () => ({
|
||||
resolver: jest.fn(),
|
||||
});
|
||||
|
||||
const startMock = () => ({
|
||||
getNavLinks$: jest.fn(() => new BehaviorSubject<NavigationLink[]>([])),
|
||||
setIsSidebarEnabled: jest.fn(),
|
||||
});
|
||||
|
||||
export const securitySolutionMock = {
|
||||
createSetup: setupMock,
|
||||
createStart: startMock,
|
||||
};
|
|
@ -310,7 +310,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.registerAppLinks(core, plugins);
|
||||
|
||||
return {
|
||||
navLinks$,
|
||||
getNavLinks$: () => navLinks$,
|
||||
setIsSidebarEnabled: (isSidebarEnabled: boolean) =>
|
||||
this.isSidebarEnabled$.next(isSidebarEnabled),
|
||||
};
|
||||
|
|
|
@ -141,7 +141,7 @@ export interface PluginSetup {
|
|||
}
|
||||
|
||||
export interface PluginStart {
|
||||
navLinks$: Observable<NavigationLink[]>;
|
||||
getNavLinks$: () => Observable<NavigationLink[]>;
|
||||
setIsSidebarEnabled: (isSidebarEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
|
|
16
x-pack/plugins/serverless/public/mocks.ts
Normal file
16
x-pack/plugins/serverless/public/mocks.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { ServerlessPluginStart } from './types';
|
||||
|
||||
const startMock = (): ServerlessPluginStart => ({
|
||||
setSideNavComponent: jest.fn(),
|
||||
});
|
||||
|
||||
export const serverlessMock = {
|
||||
createStart: startMock,
|
||||
};
|
|
@ -62,7 +62,10 @@ export class ServerlessPlugin
|
|||
core.chrome.setChromeStyle('project');
|
||||
management.setIsSidebarEnabled(false);
|
||||
|
||||
return {};
|
||||
return {
|
||||
setSideNavComponent: (sideNavigationComponent) =>
|
||||
core.chrome.project.setSideNavComponent(sideNavigationComponent),
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
*/
|
||||
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
import { SideNavComponent } from '@kbn/core-chrome-browser/src/project_navigation';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginSetup {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginStart {}
|
||||
export interface ServerlessPluginStart {
|
||||
setSideNavComponent: (navigation: SideNavComponent) => void;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
management: ManagementSetup;
|
||||
|
|
|
@ -21,5 +21,6 @@
|
|||
"@kbn/serverless-project-switcher",
|
||||
"@kbn/serverless-types",
|
||||
"@kbn/utils",
|
||||
"@kbn/core-chrome-browser",
|
||||
]
|
||||
}
|
||||
|
|
12
x-pack/plugins/serverless_security/jest.config.dev.js
Normal file
12
x-pack/plugins/serverless_security/jest.config.dev.js
Normal file
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../',
|
||||
projects: ['<rootDir>/x-pack/plugins/serverless_security/public/*/jest.config.js'],
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/serverless-security",
|
||||
"owner": "@elastic/appex-sharedux",
|
||||
"owner": "@elastic/security-solution",
|
||||
"description": "Serverless customizations for security.",
|
||||
"plugin": {
|
||||
"id": "serverlessSecurity",
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/serverless_security/public/components'],
|
||||
testMatch: [
|
||||
'<rootDir>/x-pack/plugins/serverless_security/public/components/**/*.test.{js,mjs,ts,tsx}',
|
||||
],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/serverless_security/public/components',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/serverless_security/public/components/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/*mock*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/*.d.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/*.config.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/components/index.{js,ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core/public';
|
||||
import type {
|
||||
SideNavComponent,
|
||||
SideNavCompProps,
|
||||
} from '@kbn/core-chrome-browser/src/project_navigation';
|
||||
import { ServerlessSecurityPluginStartDependencies } from '../../types';
|
||||
import { SecuritySideNavigation } from './side_navigation';
|
||||
import { KibanaServicesProvider } from '../../services';
|
||||
|
||||
export const getSecuritySideNavComponent = (
|
||||
core: CoreStart,
|
||||
pluginsStart: ServerlessSecurityPluginStartDependencies
|
||||
): SideNavComponent => {
|
||||
return (_props: SideNavCompProps) => (
|
||||
<KibanaServicesProvider core={core} pluginsStart={pluginsStart}>
|
||||
<SecuritySideNavigation />
|
||||
</KibanaServicesProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { SecuritySideNavigation } from './side_navigation';
|
||||
import { useSideNavItems, useSideNavSelectedId } from '../../hooks/use_side_nav_items';
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import { KibanaServicesProvider } from '../../services.mock';
|
||||
|
||||
jest.mock('../../hooks/use_side_nav_items');
|
||||
const mockUseSideNavItems = useSideNavItems as jest.Mock;
|
||||
const mockUseSideNavSelectedId = useSideNavSelectedId as jest.Mock;
|
||||
|
||||
const mockSolutionSideNav = jest.fn((_props: unknown) => <div data-test-subj="solutionSideNav" />);
|
||||
jest.mock('@kbn/security-solution-side-nav', () => ({
|
||||
SolutionSideNav: (props: unknown) => mockSolutionSideNav(props),
|
||||
}));
|
||||
|
||||
const sideNavItems = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '/dashboards',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: '/alerts',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
const sideNavFooterItems = [
|
||||
{
|
||||
id: SecurityPageName.administration,
|
||||
label: 'Manage',
|
||||
href: '/administration',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
mockUseSideNavItems.mockReturnValue(sideNavItems);
|
||||
mockUseSideNavSelectedId.mockReturnValue(SecurityPageName.alerts);
|
||||
|
||||
describe('SecuritySideNavigation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render loading when not items received', () => {
|
||||
mockUseSideNavItems.mockReturnValueOnce([]);
|
||||
const component = render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
expect(component.queryByTestId('sideNavLoader')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render loading when items received', () => {
|
||||
const component = render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
expect(component.queryByTestId('sideNavLoader')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the SideNav when items received', () => {
|
||||
const component = render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
expect(component.queryByTestId('solutionSideNav')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pass item props to the SolutionSideNav component', () => {
|
||||
render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
|
||||
expect(mockSolutionSideNav).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
items: sideNavItems,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass footerItems props to the SolutionSideNav component', () => {
|
||||
mockUseSideNavItems.mockReturnValueOnce(sideNavFooterItems);
|
||||
render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
|
||||
expect(mockSolutionSideNav).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
footerItems: sideNavFooterItems,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should selectedId the SolutionSideNav component', () => {
|
||||
render(<SecuritySideNavigation />, { wrapper: KibanaServicesProvider });
|
||||
|
||||
expect(mockSolutionSideNav).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
selectedId: SecurityPageName.alerts,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { EuiLoadingSpinner, useEuiTheme } from '@elastic/eui';
|
||||
import { SolutionNav } from '@kbn/shared-ux-page-solution-nav';
|
||||
import { SolutionSideNav } from '@kbn/security-solution-side-nav';
|
||||
import {
|
||||
usePartitionFooterNavItems,
|
||||
useSideNavItems,
|
||||
useSideNavSelectedId,
|
||||
} from '../../hooks/use_side_nav_items';
|
||||
|
||||
export const SecuritySideNavigation: React.FC = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const sideNavItems = useSideNavItems();
|
||||
const selectedId = useSideNavSelectedId(sideNavItems);
|
||||
const [items, footerItems] = usePartitionFooterNavItems(sideNavItems);
|
||||
|
||||
const isLoading = items.length === 0 && footerItems.length === 0;
|
||||
|
||||
return isLoading ? (
|
||||
<EuiLoadingSpinner size="m" data-test-subj="sideNavLoader" />
|
||||
) : (
|
||||
<SolutionNav
|
||||
canBeCollapsed={false}
|
||||
name={'Security'}
|
||||
icon={'logoSecurity'}
|
||||
children={
|
||||
<SolutionSideNav
|
||||
items={items}
|
||||
footerItems={footerItems}
|
||||
selectedId={selectedId}
|
||||
panelTopOffset={`calc(${euiTheme.size.l} * 2)`}
|
||||
/>
|
||||
}
|
||||
closeFlyoutButtonPosition={'inside'}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 type { GetLinkProps } from '../use_link_props';
|
||||
|
||||
export const getLinkProps = jest.fn(() => ({
|
||||
href: '/test-href',
|
||||
onClick: jest.fn(),
|
||||
}));
|
||||
|
||||
export const useLinkProps: GetLinkProps = getLinkProps;
|
||||
export const useGetLinkProps: () => GetLinkProps = jest.fn(() => getLinkProps);
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 type { SolutionSideNavItem } from '@kbn/security-solution-side-nav';
|
||||
|
||||
const { usePartitionFooterNavItems: originalUsePartitionFooterNavItems } =
|
||||
jest.requireActual('../use_side_nav_items');
|
||||
|
||||
export const useSideNavItems = jest.fn((): SolutionSideNavItem[] => []);
|
||||
|
||||
export const usePartitionFooterNavItems = jest.fn(
|
||||
(sideNavItems: SolutionSideNavItem[]): [SolutionSideNavItem[], SolutionSideNavItem[]] =>
|
||||
// Same implementation as original for convenience. Can be overridden in tests if needed
|
||||
originalUsePartitionFooterNavItems(sideNavItems)
|
||||
);
|
||||
|
||||
export const useSideNavSelectedId = jest.fn((_sideNavItems: SolutionSideNavItem[]): string => '');
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/serverless_security/public/hooks'],
|
||||
testMatch: [
|
||||
'<rootDir>/x-pack/plugins/serverless_security/public/hooks/**/*.test.{js,mjs,ts,tsx}',
|
||||
],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/serverless_security/public/hooks',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/serverless_security/public/hooks/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/*mock*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/*.d.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/*.config.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/hooks/index.{js,ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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 { MouseEvent } from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { APP_UI_ID, SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import { KibanaServicesProvider, servicesMocks } from '../services.mock';
|
||||
import { useGetLinkProps, useLinkProps } from './use_link_props';
|
||||
|
||||
const { getUrlForApp: mockGetUrlForApp, navigateToUrl: mockNavigateToUrl } =
|
||||
servicesMocks.application;
|
||||
|
||||
const href = '/app/security/test';
|
||||
mockGetUrlForApp.mockReturnValue(href);
|
||||
|
||||
describe('useLinkProps', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return link props', async () => {
|
||||
const { result } = renderHook(useLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const linkProps = result.current;
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: undefined,
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call navigate when clicked normally', async () => {
|
||||
const ev = { preventDefault: jest.fn() } as unknown as MouseEvent;
|
||||
const { result } = renderHook(useLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const { onClick } = result.current;
|
||||
onClick(ev);
|
||||
|
||||
expect(mockNavigateToUrl).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigateToUrl).toHaveBeenCalledWith(href);
|
||||
});
|
||||
|
||||
it('should not call navigate when clicked with modifiers', async () => {
|
||||
const ev = { preventDefault: jest.fn(), ctrlKey: true } as unknown as MouseEvent;
|
||||
const { result } = renderHook(useLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const { onClick } = result.current;
|
||||
onClick(ev);
|
||||
|
||||
expect(mockNavigateToUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return link props passing deepLink', async () => {
|
||||
const { result } = renderHook(useLinkProps, {
|
||||
wrapper: KibanaServicesProvider,
|
||||
initialProps: { deepLinkId: SecurityPageName.alerts },
|
||||
});
|
||||
|
||||
const linkProps = result.current;
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return link props passing deepLink and path', async () => {
|
||||
const { result } = renderHook(useLinkProps, {
|
||||
wrapper: KibanaServicesProvider,
|
||||
initialProps: { deepLinkId: SecurityPageName.alerts, path: '/test' },
|
||||
});
|
||||
|
||||
const linkProps = result.current;
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: '/test',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useGetLinkProps', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return link props', async () => {
|
||||
const { result } = renderHook(useGetLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const linkProps = result.current({});
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: undefined,
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call navigate when clicked normally', async () => {
|
||||
const ev = { preventDefault: jest.fn() } as unknown as MouseEvent;
|
||||
const { result } = renderHook(useGetLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const { onClick } = result.current({});
|
||||
onClick(ev);
|
||||
|
||||
expect(mockNavigateToUrl).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigateToUrl).toHaveBeenCalledWith(href);
|
||||
});
|
||||
|
||||
it('should not call navigate when clicked with modifiers', async () => {
|
||||
const ev = { preventDefault: jest.fn(), ctrlKey: true } as unknown as MouseEvent;
|
||||
const { result } = renderHook(useGetLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const { onClick } = result.current({});
|
||||
onClick(ev);
|
||||
|
||||
expect(mockNavigateToUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return link props passing deepLink', async () => {
|
||||
const { result } = renderHook(useGetLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const linkProps = result.current({ deepLinkId: SecurityPageName.alerts });
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return link props passing deepLink and path', async () => {
|
||||
const { result } = renderHook(useGetLinkProps, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const linkProps = result.current({ deepLinkId: SecurityPageName.alerts, path: '/test' });
|
||||
|
||||
expect(linkProps).toEqual({ href, onClick: expect.any(Function) });
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetUrlForApp).toHaveBeenCalledWith(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: '/test',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { APP_UI_ID, type SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import { useMemo, useCallback, type MouseEventHandler, type MouseEvent } from 'react';
|
||||
import { useKibana, type Services } from '../services';
|
||||
|
||||
interface LinkProps {
|
||||
onClick: MouseEventHandler;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface GetLinkPropsParams {
|
||||
deepLinkId?: SecurityPageName;
|
||||
path?: string;
|
||||
appId?: string;
|
||||
onClick?: MouseEventHandler;
|
||||
}
|
||||
|
||||
export type GetLinkProps = (params: GetLinkPropsParams) => LinkProps;
|
||||
|
||||
export const useLinkProps: GetLinkProps = (props) => {
|
||||
const { application } = useKibana().services;
|
||||
return useMemo(() => getLinkProps({ ...props, application }), [application, props]);
|
||||
};
|
||||
|
||||
export const useGetLinkProps: () => GetLinkProps = () => {
|
||||
const { application } = useKibana().services;
|
||||
return useCallback<GetLinkProps>(
|
||||
(props) => getLinkProps({ ...props, application }),
|
||||
[application]
|
||||
);
|
||||
};
|
||||
|
||||
const getLinkProps = ({
|
||||
deepLinkId,
|
||||
path,
|
||||
onClick: onClickProps,
|
||||
appId = APP_UI_ID,
|
||||
application,
|
||||
}: GetLinkPropsParams & { application: Services['application'] }): LinkProps => {
|
||||
const { getUrlForApp, navigateToUrl } = application;
|
||||
const url = getUrlForApp(appId, { deepLinkId, path });
|
||||
return {
|
||||
href: url,
|
||||
onClick: (ev) => {
|
||||
if (isModifiedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
navigateToUrl(url);
|
||||
if (onClickProps) {
|
||||
onClickProps(ev);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const isModifiedEvent = (event: MouseEvent) =>
|
||||
event.metaKey || event.altKey || event.ctrlKey || event.shiftKey;
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { useKibana } from '../services';
|
||||
|
||||
export const useNavLinks = () => {
|
||||
const { securitySolution } = useKibana().services;
|
||||
const { getNavLinks$ } = securitySolution;
|
||||
const navLinks$ = useMemo(() => getNavLinks$(), [getNavLinks$]);
|
||||
return useObservable(navLinks$, []);
|
||||
};
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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 {
|
||||
usePartitionFooterNavItems,
|
||||
useSideNavItems,
|
||||
useSideNavSelectedId,
|
||||
} from './use_side_nav_items';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import type { NavigationLink } from '@kbn/security-solution-plugin/public/common/links/types';
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import { KibanaServicesProvider, servicesMocks } from '../services.mock';
|
||||
|
||||
jest.mock('./use_link_props');
|
||||
|
||||
const mockNavLinks = jest.fn((): NavigationLink[] => []);
|
||||
servicesMocks.securitySolution.getNavLinks$.mockImplementation(
|
||||
() => new BehaviorSubject(mockNavLinks())
|
||||
);
|
||||
|
||||
const mockUseLocation = jest.fn(() => ({ pathname: '/' }));
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: () => mockUseLocation(),
|
||||
}));
|
||||
|
||||
describe('useSideNavItems', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty items', async () => {
|
||||
const { result } = renderHook(useSideNavItems, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const items = result.current;
|
||||
|
||||
expect(items).toEqual([]);
|
||||
expect(servicesMocks.securitySolution.getNavLinks$).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return main items', async () => {
|
||||
mockNavLinks.mockReturnValueOnce([
|
||||
{ id: SecurityPageName.alerts, title: 'Alerts' },
|
||||
{ id: SecurityPageName.case, title: 'Cases' },
|
||||
]);
|
||||
const { result } = renderHook(useSideNavItems, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const items = result.current;
|
||||
expect(items).toEqual([
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: expect.any(String),
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.case,
|
||||
label: 'Cases',
|
||||
href: expect.any(String),
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return secondary items', async () => {
|
||||
mockNavLinks.mockReturnValueOnce([
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
title: 'Dashboards',
|
||||
links: [{ id: SecurityPageName.detectionAndResponse, title: 'Detection & Response' }],
|
||||
},
|
||||
]);
|
||||
const { result } = renderHook(useSideNavItems, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const items = result.current;
|
||||
expect(items).toEqual([
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: expect.any(String),
|
||||
onClick: expect.any(Function),
|
||||
items: [
|
||||
{
|
||||
id: SecurityPageName.detectionAndResponse,
|
||||
label: 'Detection & Response',
|
||||
href: expect.any(String),
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return get started link', async () => {
|
||||
mockNavLinks.mockReturnValueOnce([
|
||||
{
|
||||
id: SecurityPageName.landing,
|
||||
title: 'Get Started',
|
||||
},
|
||||
]);
|
||||
const { result } = renderHook(useSideNavItems, { wrapper: KibanaServicesProvider });
|
||||
|
||||
const items = result.current;
|
||||
|
||||
expect(items).toEqual([
|
||||
{
|
||||
id: SecurityPageName.landing,
|
||||
label: 'GET STARTED',
|
||||
href: expect.any(String),
|
||||
onClick: expect.any(Function),
|
||||
labelSize: 'xs',
|
||||
iconType: 'launch',
|
||||
appendSeparator: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePartitionFooterNavItems', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should partition main items only', async () => {
|
||||
const mainInputItems = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
const { result } = renderHook(usePartitionFooterNavItems, {
|
||||
initialProps: mainInputItems,
|
||||
});
|
||||
|
||||
const [items, footerItems] = result.current;
|
||||
|
||||
expect(items).toEqual(mainInputItems);
|
||||
expect(footerItems).toEqual([]);
|
||||
});
|
||||
|
||||
it('should partition footer items only', async () => {
|
||||
const footerInputItems = [
|
||||
{
|
||||
id: SecurityPageName.landing,
|
||||
label: 'GET STARTED',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.administration,
|
||||
label: 'Manage',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
const { result } = renderHook(usePartitionFooterNavItems, {
|
||||
initialProps: footerInputItems,
|
||||
});
|
||||
|
||||
const [items, footerItems] = result.current;
|
||||
|
||||
expect(items).toEqual([]);
|
||||
expect(footerItems).toEqual(footerInputItems);
|
||||
});
|
||||
|
||||
it('should partition main and footer items', async () => {
|
||||
const mainInputItems = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
const footerInputItems = [
|
||||
{
|
||||
id: SecurityPageName.landing,
|
||||
label: 'GET STARTED',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.administration,
|
||||
label: 'Manage',
|
||||
href: '',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
const { result } = renderHook(usePartitionFooterNavItems, {
|
||||
initialProps: [...mainInputItems, ...footerInputItems],
|
||||
});
|
||||
|
||||
const [items, footerItems] = result.current;
|
||||
|
||||
expect(items).toEqual(mainInputItems);
|
||||
expect(footerItems).toEqual(footerInputItems);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSideNavSelectedId', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty string when no item selected', async () => {
|
||||
const items = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '/app/security/dashboards',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: '/app/security/alerts',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
const { result } = renderHook(useSideNavSelectedId, {
|
||||
wrapper: KibanaServicesProvider,
|
||||
initialProps: items,
|
||||
});
|
||||
|
||||
const selectedId = result.current;
|
||||
expect(selectedId).toEqual('');
|
||||
});
|
||||
|
||||
it('should return the item with path selected', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/app/security/alerts' });
|
||||
const items = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '/app/security/dashboards',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
label: 'Alerts',
|
||||
href: '/app/security/alerts',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
const { result } = renderHook(useSideNavSelectedId, {
|
||||
wrapper: KibanaServicesProvider,
|
||||
initialProps: items,
|
||||
});
|
||||
|
||||
const selectedId = result.current;
|
||||
expect(selectedId).toEqual(SecurityPageName.alerts);
|
||||
});
|
||||
|
||||
it('should return the main item when nested path selected', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/app/security/detection_response' });
|
||||
const items = [
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
label: 'Dashboards',
|
||||
href: '/app/security/dashboards',
|
||||
onClick: jest.fn(),
|
||||
items: [
|
||||
{
|
||||
id: SecurityPageName.detectionAndResponse,
|
||||
label: 'Detection & Response',
|
||||
href: '/app/security/detection_response',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const { result } = renderHook(useSideNavSelectedId, {
|
||||
wrapper: KibanaServicesProvider,
|
||||
initialProps: items,
|
||||
});
|
||||
|
||||
const selectedId = result.current;
|
||||
expect(selectedId).toEqual(SecurityPageName.dashboards);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { partition } from 'lodash/fp';
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import type { SolutionSideNavItem } from '@kbn/security-solution-side-nav';
|
||||
import { useKibana } from '../services';
|
||||
import { useGetLinkProps } from './use_link_props';
|
||||
import { useNavLinks } from './use_nav_links';
|
||||
|
||||
const isFooterNavItem = (id: string) =>
|
||||
id === SecurityPageName.landing || id === SecurityPageName.administration;
|
||||
|
||||
const isGetStartedNavItem = (id: string) => id === SecurityPageName.landing;
|
||||
|
||||
// DFS for the sideNavItem matching the current `pathname`, returns all item hierarchy when found
|
||||
const findItemsByPath = (
|
||||
sideNavItems: SolutionSideNavItem[],
|
||||
pathname: string
|
||||
): SolutionSideNavItem[] => {
|
||||
for (const sideNavItem of sideNavItems) {
|
||||
if (sideNavItem.items?.length) {
|
||||
const found = findItemsByPath(sideNavItem.items, pathname);
|
||||
if (found.length) {
|
||||
found.unshift(sideNavItem);
|
||||
return found;
|
||||
}
|
||||
}
|
||||
if (matchPath(pathname, { path: sideNavItem.href })) {
|
||||
return [sideNavItem];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all the formatted SideNavItems, including external links
|
||||
*/
|
||||
export const useSideNavItems = (): SolutionSideNavItem[] => {
|
||||
const navLinks = useNavLinks();
|
||||
const getLinkProps = useGetLinkProps();
|
||||
|
||||
const securitySideNavItems = useMemo(
|
||||
() =>
|
||||
navLinks.reduce<SolutionSideNavItem[]>((items, navLink) => {
|
||||
if (navLink.disabled) {
|
||||
return items;
|
||||
}
|
||||
if (isGetStartedNavItem(navLink.id)) {
|
||||
items.push({
|
||||
id: navLink.id,
|
||||
label: navLink.title.toUpperCase(),
|
||||
...getLinkProps({ deepLinkId: navLink.id }),
|
||||
labelSize: 'xs',
|
||||
iconType: 'launch',
|
||||
appendSeparator: true,
|
||||
});
|
||||
} else {
|
||||
// default sideNavItem formatting
|
||||
items.push({
|
||||
id: navLink.id,
|
||||
label: navLink.title,
|
||||
...getLinkProps({ deepLinkId: navLink.id }),
|
||||
...(navLink.categories?.length && { categories: navLink.categories }),
|
||||
...(navLink.links?.length && {
|
||||
items: navLink.links.reduce<SolutionSideNavItem[]>((acc, current) => {
|
||||
if (!current.disabled) {
|
||||
acc.push({
|
||||
id: current.id,
|
||||
label: current.title,
|
||||
description: current.description,
|
||||
isBeta: current.isBeta,
|
||||
betaOptions: current.betaOptions,
|
||||
...getLinkProps({ deepLinkId: current.id }),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []),
|
||||
}),
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}, []),
|
||||
[getLinkProps, navLinks]
|
||||
);
|
||||
|
||||
const sideNavItems = useAddExternalSideNavItems(securitySideNavItems);
|
||||
|
||||
return sideNavItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param securitySideNavItems the sideNavItems for Security pages
|
||||
* @returns sideNavItems with Security and external links
|
||||
*/
|
||||
const useAddExternalSideNavItems = (securitySideNavItems: SolutionSideNavItem[]) => {
|
||||
const sideNavItemsWithExternals = useMemo(
|
||||
() => [
|
||||
...securitySideNavItems,
|
||||
// TODO: add external links. e.g.:
|
||||
// {
|
||||
// id: 'ml',
|
||||
// label: 'Machine Learning Jobs',
|
||||
// ...getLinkProps({ appId: 'ml', path: '/jobs' }),
|
||||
// links: [...]
|
||||
// },
|
||||
],
|
||||
[securitySideNavItems]
|
||||
);
|
||||
|
||||
return sideNavItemsWithExternals;
|
||||
};
|
||||
|
||||
/**
|
||||
* Partitions the sideNavItems into main and footer SideNavItems
|
||||
* @param sideNavItems array for all SideNavItems
|
||||
* @returns `[items, footerItems]` to be used in the side navigation component
|
||||
*/
|
||||
export const usePartitionFooterNavItems = (
|
||||
sideNavItems: SolutionSideNavItem[]
|
||||
): [SolutionSideNavItem[], SolutionSideNavItem[]] =>
|
||||
useMemo(() => partition((item) => !isFooterNavItem(item.id), sideNavItems), [sideNavItems]);
|
||||
|
||||
/**
|
||||
* Returns the selected item id, which is the root item in the links hierarchy
|
||||
*/
|
||||
export const useSideNavSelectedId = (sideNavItems: SolutionSideNavItem[]): string => {
|
||||
const { http } = useKibana().services;
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const selectedId: string = useMemo(() => {
|
||||
const [rootNavItem] = findItemsByPath(sideNavItems, http.basePath.prepend(pathname));
|
||||
return rootNavItem?.id ?? '';
|
||||
}, [sideNavItems, pathname, http]);
|
||||
|
||||
return selectedId;
|
||||
};
|
27
x-pack/plugins/serverless_security/public/jest.config.js
Normal file
27
x-pack/plugins/serverless_security/public/jest.config.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
/** all nested directories have their own Jest config file */
|
||||
testMatch: ['<rootDir>/x-pack/plugins/serverless_security/public/*.test.{js,mjs,ts,tsx}'],
|
||||
roots: ['<rootDir>/x-pack/plugins/serverless_security/public'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/serverless_security/public',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/serverless_security/public/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/*mock*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/*.d.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/*.config.ts',
|
||||
'!<rootDir>/x-pack/plugins/serverless_security/public/index.{js,ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { getSecuritySideNavComponent } from './components/side_navigation';
|
||||
import {
|
||||
ServerlessSecurityPluginSetup,
|
||||
ServerlessSecurityPluginStart,
|
||||
|
@ -30,10 +31,14 @@ export class ServerlessSecurityPlugin
|
|||
}
|
||||
|
||||
public start(
|
||||
_core: CoreStart,
|
||||
{ securitySolution }: ServerlessSecurityPluginStartDependencies
|
||||
core: CoreStart,
|
||||
startDeps: ServerlessSecurityPluginStartDependencies
|
||||
): ServerlessSecurityPluginStart {
|
||||
const { securitySolution, serverless } = startDeps;
|
||||
|
||||
securitySolution.setIsSidebarEnabled(false);
|
||||
serverless.setSideNavComponent(getSecuritySideNavComponent(core, startDeps));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
26
x-pack/plugins/serverless_security/public/services.mock.tsx
Normal file
26
x-pack/plugins/serverless_security/public/services.mock.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { serverlessMock } from '@kbn/serverless/public/mocks';
|
||||
import { securityMock } from '@kbn/security-plugin/public/mocks';
|
||||
import { securitySolutionMock } from '@kbn/security-solution-plugin/public/mocks';
|
||||
|
||||
export const servicesMocks = {
|
||||
...coreMock.createStart(),
|
||||
serverless: serverlessMock.createStart(),
|
||||
security: securityMock.createStart(),
|
||||
securitySolution: securitySolutionMock.createStart(),
|
||||
};
|
||||
|
||||
export const KibanaServicesProvider = React.memo(({ children }) => (
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={servicesMocks}>{children}</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
));
|
26
x-pack/plugins/serverless_security/public/services.tsx
Normal file
26
x-pack/plugins/serverless_security/public/services.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core/public';
|
||||
import React from 'react';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
useKibana as useKibanaReact,
|
||||
} from '@kbn/kibana-react-plugin/public';
|
||||
import type { ServerlessSecurityPluginStartDependencies } from './types';
|
||||
|
||||
export type Services = CoreStart & ServerlessSecurityPluginStartDependencies;
|
||||
|
||||
export const KibanaServicesProvider: React.FC<{
|
||||
core: CoreStart;
|
||||
pluginsStart: ServerlessSecurityPluginStartDependencies;
|
||||
}> = ({ core, pluginsStart, children }) => {
|
||||
const services: Services = { ...core, ...pluginsStart };
|
||||
return <KibanaContextProvider services={services}>{children}</KibanaContextProvider>;
|
||||
};
|
||||
|
||||
export const useKibana = () => useKibanaReact<Services>();
|
|
@ -20,5 +20,10 @@
|
|||
"@kbn/security-plugin",
|
||||
"@kbn/security-solution-plugin",
|
||||
"@kbn/serverless",
|
||||
"@kbn/shared-ux-page-solution-nav",
|
||||
"@kbn/security-solution-side-nav",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/i18n-react",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue