Skip to content

Commit

Permalink
[Security Solution][Serverless] Implements panelContentProvider on th…
Browse files Browse the repository at this point in the history
…e DefaultNavigation (#169270)

## Summary

Implements the `panelContentProvider` for the `DefaultNavigation`
component, so the content of the panels when open is provided by the
Security Solution plugin.

In order to test it, the experimental flag needs to be enabled.
In `config/serverless.security.yml` add:

```
xpack.securitySolutionServerless.enableExperimental: ['platformNavEnabled']
```

## Screenshot

<img width="1718" alt="Captura de pantalla 2023-10-18 a les 18 38 04"
src="https://github.com/elastic/kibana/assets/17747913/5022a7d9-c619-4dbb-87cf-ee3ed0090853">

(The vertical separation of the main nav links is still not implemented
by the DefaultNavigation)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
semd and kibanamachine committed Oct 23, 2023
1 parent 17c78db commit a8a22c3
Show file tree
Hide file tree
Showing 18 changed files with 455 additions and 305 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const createStartContractMock = () => {
startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false));
startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([]));
startContract.hasHeaderBanner$.mockReturnValue(new BehaviorSubject(false));
startContract.getIsSideNavCollapsed$.mockReturnValue(new BehaviorSubject(false));
return startContract;
};

Expand Down
2 changes: 1 addition & 1 deletion x-pack/packages/security-solution/side_nav/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export { SolutionSideNavPanel } from './src/solution_side_nav_panel';
export { SolutionSideNavPanelContent } from './src/solution_side_nav_panel';
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ import {
accordionButtonClassName,
} from './solution_side_nav_panel.styles';

export interface SolutionSideNavPanelProps {
onClose: () => void;
onOutsideClick: () => void;
export interface SolutionSideNavPanelContentProps {
title: string;
onClose: () => void;
items: SolutionSideNavItem[];
categories?: LinkCategories;
}
export interface SolutionSideNavPanelProps extends SolutionSideNavPanelContentProps {
onOutsideClick: () => void;
bottomOffset?: string;
topOffset?: string;
}
Expand Down Expand Up @@ -85,7 +87,6 @@ export const SolutionSideNavPanel: React.FC<SolutionSideNavPanelProps> = React.m
$topOffset,
});
const panelClasses = classNames(panelClassName, 'eui-yScroll', solutionSideNavPanelStyles);
const titleClasses = classNames(SolutionSideNavTitleStyles(euiTheme));

// ESC key closes PanelNav
const onKeyDown = useCallback(
Expand All @@ -110,24 +111,12 @@ export const SolutionSideNavPanel: React.FC<SolutionSideNavPanelProps> = React.m
paddingSize="m"
data-test-subj="solutionSideNavPanel"
>
<EuiFlexGroup direction="column" gutterSize="m" alignItems="flexStart">
<EuiFlexItem>
<EuiTitle size="xs" className={titleClasses}>
<strong>{title}</strong>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem style={{ width: '100%' }}>
{categories ? (
<SolutionSideNavPanelCategories
categories={categories}
items={items}
onClose={onClose}
/>
) : (
<SolutionSideNavPanelItems items={items} onClose={onClose} />
)}
</EuiFlexItem>
</EuiFlexGroup>
<SolutionSideNavPanelContent
title={title}
categories={categories}
items={items}
onClose={onClose}
/>
</EuiPanel>
</EuiOutsideClickDetector>
</EuiFocusTrap>
Expand All @@ -137,6 +126,33 @@ export const SolutionSideNavPanel: React.FC<SolutionSideNavPanelProps> = React.m
}
);

export const SolutionSideNavPanelContent: React.FC<SolutionSideNavPanelContentProps> = React.memo(
function SolutionSideNavPanelContent({ title, onClose, categories, items }) {
const { euiTheme } = useEuiTheme();
const titleClasses = classNames(SolutionSideNavTitleStyles(euiTheme));
return (
<EuiFlexGroup direction="column" gutterSize="m" alignItems="flexStart">
<EuiFlexItem>
<EuiTitle size="xs" className={titleClasses}>
<strong>{title}</strong>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem style={{ width: '100%' }}>
{categories ? (
<SolutionSideNavPanelCategories
categories={categories}
items={items}
onClose={onClose}
/>
) : (
<SolutionSideNavPanelItems items={items} onClose={onClose} />
)}
</EuiFlexItem>
</EuiFlexGroup>
);
}
);

interface SolutionSideNavPanelCategoriesProps {
categories: LinkCategories;
items: SolutionSideNavItem[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,5 @@ export const TelemetryContextProvider: FC<TelemetryProviderProps> = ({ children,
};

export const useTelemetryContext = () => {
const context = useContext(TelemetryContext);
if (!context) {
throw new Error('No TelemetryContext found.');
}
return context;
return useContext(TelemetryContext) ?? {};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import {
SecurityPageName,
LinkCategoryType,
type LinkCategory,
type SeparatorLinkCategory,
} from '@kbn/security-solution-navigation';
import { ExternalPageName } from '../links/constants';
import { ExternalPageName } from './links/constants';
import type { ProjectPageName } from './links/types';

export const CATEGORIES: SeparatorLinkCategory[] = [
export const CATEGORIES: Array<SeparatorLinkCategory<ProjectPageName>> = [
{
type: LinkCategoryType.separator,
linkIds: [ExternalPageName.discover, SecurityPageName.dashboards],
Expand Down Expand Up @@ -43,3 +46,24 @@ export const CATEGORIES: SeparatorLinkCategory[] = [
linkIds: [SecurityPageName.mlLanding],
},
];

export const FOOTER_CATEGORIES: Array<LinkCategory<ProjectPageName>> = [
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.landing, ExternalPageName.devTools],
},
{
type: LinkCategoryType.accordion,
label: i18n.translate('xpack.securitySolutionServerless.nav.projectSettings.title', {
defaultMessage: 'Project settings',
}),
iconType: 'gear',
linkIds: [
ExternalPageName.management,
ExternalPageName.integrationsSecurity,
ExternalPageName.cloudUsersAndRoles,
ExternalPageName.cloudPerformance,
ExternalPageName.cloudBilling,
],
},
];

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { APP_PATH } from '@kbn/security-solution-plugin/common';
import type { CoreSetup } from '@kbn/core/public';
import type { SecuritySolutionServerlessPluginSetupDeps } from '../types';
import type { Services } from '../common/services';
import { withServicesProvider } from '../common/services';
import { subscribeBreadcrumbs } from './breadcrumbs';
import { ProjectNavigationTree } from './navigation_tree';
import { getSecuritySideNavComponent } from './side_navigation';
import { getDefaultNavigationComponent } from './default_navigation';
import { SecuritySideNavComponent } from './project_navigation';
import { projectAppLinksSwitcher } from './links/app_links';
import { formatProjectDeepLinks } from './links/deep_links';

Expand All @@ -28,15 +29,14 @@ export const startNavigation = (services: Services) => {
const { serverless, management } = services;
serverless.setProjectHome(APP_PATH);

management.setupCardsNavigation({ enabled: true });

const projectNavigationTree = new ProjectNavigationTree(services);

if (services.experimentalFeatures.platformNavEnabled) {
projectNavigationTree.getNavigationTree$().subscribe((navigationTree) => {
serverless.setSideNavComponent(getDefaultNavigationComponent(navigationTree, services));
});
const SideNavComponentWithServices = withServicesProvider(SecuritySideNavComponent, services);
serverless.setSideNavComponent(SideNavComponentWithServices);
} else {
management.setupCardsNavigation({ enabled: true });

projectNavigationTree.getChromeNavigationTree$().subscribe((chromeNavigationTree) => {
serverless.setNavigation({ navigationTree: chromeNavigationTree });
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ export const projectSettingsNavLinks: ProjectNavigationLink[] = [
id: ExternalPageName.cloudUsersAndRoles,
title: i18n.CLOUD_USERS_ROLES_TITLE,
},
{
id: ExternalPageName.cloudPerformance,
title: i18n.CLOUD_PERFORMANCE_TITLE,
},
{
id: ExternalPageName.cloudBilling,
title: i18n.CLOUD_BILLING_TITLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/

import { APP_UI_ID } from '@kbn/security-solution-plugin/common';
import { APP_UI_ID, SecurityPageName } from '@kbn/security-solution-plugin/common';
import { ExternalPageName } from './constants';
import type { GetCloudUrl, ProjectPageName } from './types';

export const getNavLinkIdFromProjectPageName = (projectNavLinkId: ProjectPageName): string => {
Expand Down Expand Up @@ -42,3 +43,16 @@ export const getCloudUrl: GetCloudUrl = (cloudUrlKey, cloud) => {
return undefined;
}
};

/**
* Defines the navigation items that should be in the footer of the side navigation.
* @todo Make it a new property in the `NavigationLink` type `position?: 'top' | 'bottom' (default: 'top')`
*/
export const isBottomNavItemId = (id: string) =>
id === SecurityPageName.landing ||
id === ExternalPageName.devTools ||
id === ExternalPageName.management ||
id === ExternalPageName.integrationsSecurity ||
id === ExternalPageName.cloudUsersAndRoles ||
id === ExternalPageName.cloudPerformance ||
id === ExternalPageName.cloudBilling;
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,10 @@

import type { Observable } from 'rxjs';
import { map } from 'rxjs';
import type { NavigationTreeDefinition } from '@kbn/shared-ux-chrome-navigation';
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
import type { LinkCategory } from '@kbn/security-solution-navigation';
import type { Services } from '../../common/services';
import type { ProjectNavLinks, ProjectPageName } from '../links/types';
import type { ProjectNavLinks } from '../links/types';
import { getFormatChromeProjectNavNodes } from './chrome_navigation_tree';
import { formatNavigationTree } from './navigation_tree';
import { CATEGORIES } from '../side_navigation/categories';

const projectCategories = CATEGORIES as Array<LinkCategory<ProjectPageName>>;

/**
* This class is temporary until we can remove the chrome navigation tree and use only the formatNavigationTree
Expand All @@ -29,12 +23,6 @@ export class ProjectNavigationTree {
this.projectNavLinks$ = getProjectNavLinks$();
}

public getNavigationTree$(): Observable<NavigationTreeDefinition> {
return this.projectNavLinks$.pipe(
map((projectNavLinks) => formatNavigationTree(projectNavLinks, projectCategories))
);
}

public getChromeNavigationTree$(): Observable<ChromeProjectNavigationNode[]> {
const formatChromeProjectNavNodes = getFormatChromeProjectNavNodes(this.services);
return this.projectNavLinks$.pipe(
Expand Down
Loading

0 comments on commit a8a22c3

Please sign in to comment.