Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC][Security Solution] Serverless navigation #30

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
48bf361
Add 'enabled' config option to multiple plugins for projects.
clintandrewhall Feb 24, 2023
023ed3a
Add ability to hide Observability navigation
clintandrewhall Feb 24, 2023
47f1bb6
Create Serverless o11y plugin; hide internal nav via API
clintandrewhall Feb 24, 2023
31d27d8
Add ability to hide Security navigation
clintandrewhall Feb 26, 2023
2cf8b52
Create Serverless security plugin; hide internal nav via API
clintandrewhall Feb 26, 2023
e601b89
Create 'solution' chrome style, add API to place solution navigation
clintandrewhall Feb 26, 2023
fff494c
Create Serverless plugin; alias core navigation to local API
clintandrewhall Feb 26, 2023
63bc063
Add Serverless as dependency to existing plugins; wire up navigation …
clintandrewhall Feb 26, 2023
ec92b8a
Add hack for SCSS var for demo purposes
clintandrewhall Feb 26, 2023
26a08ec
Merge branch 'serverless/poc/navigation' of github.com:clintandrewhal…
semd Mar 1, 2023
3468498
migration of security nav to package
semd Mar 2, 2023
a4d202f
nav links definition
semd Mar 2, 2023
f0515bd
integrate navigation to serverless security
semd Mar 2, 2023
e723dea
integrate navigation to serverless security
semd Mar 2, 2023
8e34c7f
discover item for testing
semd Mar 2, 2023
5b84d8f
Delete README.mdx
semd Mar 2, 2023
1f25dee
side nav collapsible implemented using local storage
semd Mar 6, 2023
7800bdd
Merge branch 'security_serverless_navigation_poc' of https://github.c…
semd Mar 6, 2023
5ad4257
missed rename
semd Mar 6, 2023
0385ef5
use core history for location updates
semd Mar 8, 2023
3d9eaaf
standarize sideNavigation items and remove special case
semd Mar 8, 2023
16e2670
merge clint's branch
semd Mar 14, 2023
5c0ff79
merge clint's updated branch
semd Mar 14, 2023
2c8537e
use to new package nav
semd Mar 14, 2023
fbe43c3
package change
semd Mar 14, 2023
a5fe06a
sync
semd Mar 14, 2023
fd091dc
newline
semd Mar 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/serverless.security.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
enterpriseSearch.enabled: false
xpack.apm.enabled: false
xpack.canvas.enabled: false
xpack.observability.enabled: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,8 @@
*/

import React from 'react';
import {
EuiButtonIcon,
EuiCollapsibleNav,
EuiHeader,
EuiHeaderLogo,
EuiHeaderSection,
EuiHeaderSectionItem,
EuiThemeProvider,
useEuiTheme,
} from '@elastic/eui';
import { Router } from 'react-router-dom';
import { EuiHeader, EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem } from '@elastic/eui';
import {
ChromeBreadcrumb,
ChromeGlobalHelpExtensionMenuLink,
Expand Down Expand Up @@ -51,8 +43,6 @@ export const SolutionHeader = ({
navigation,
...observables
}: Props) => {
const { euiTheme, colorMode } = useEuiTheme();

const renderLogo = () => (
<EuiHeaderLogo
iconType="logoElastic"
Expand Down Expand Up @@ -91,28 +81,7 @@ export const SolutionHeader = ({
</EuiHeaderSectionItem>
</EuiHeaderSection>
</EuiHeader>
<EuiThemeProvider colorMode={colorMode === 'DARK' ? 'LIGHT' : 'DARK'}>
<EuiCollapsibleNav
css={{
borderInlineEndWidth: 1,
background: euiTheme.colors.darkestShade,
}}
isOpen={true}
onClose={() => {}}
closeButtonProps={{ iconType: 'menuLeft' }}
showButtonIfDocked={true}
isDocked={true}
size={248}
hideCloseButton={false}
button={
<span css={{ marginLeft: -40, marginTop: 16, position: 'fixed', zIndex: 1000 }}>
<EuiButtonIcon iconType="menuLeft" aria-label="Open nav" color="text" />
</span>
}
>
{navigation}
</EuiCollapsibleNav>
</EuiThemeProvider>
<Router history={application.history}>{navigation}</Router>
</>
);
};
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// TODO(jbudz): should be removed when upgrading to [email protected]
// this is a skip for the errors created when typechecking with isolatedModules
export {};
export { APP_UI_ID, SecurityPageName } from './constants';
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';

// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ import {
USERS_PATH,
} from '../../../common/constants';
import type { ExperimentalFeatures } from '../../../common/experimental_features';
import { hasCapabilities, subscribeAppLinks } from '../../common/links';
import { appLinks$, hasCapabilities } from '../../common/links';
import type { AppLinkItems } from '../../common/links/types';

export const FEATURE = {
Expand Down Expand Up @@ -630,7 +630,7 @@ const formatDeepLinks = (appLinks: AppLinkItems): AppDeepLink[] =>
* Registers any change in appLinks to be updated in app deepLinks
*/
export const registerDeepLinksUpdater = (appUpdater$: Subject<AppUpdater>): Subscription => {
return subscribeAppLinks((appLinks) => {
return appLinks$.subscribe((appLinks) => {
appUpdater$.next(() => ({
navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent main security link to switch to visible after update
deepLinks: formatDeepLinks(appLinks),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { SecurityPageName } from '../../../../app/types';
import { getAncestorLinksInfo } from '../../../links';
import { useRouteSpy } from '../../../utils/route/use_route_spy';
import { useGetSecuritySolutionLinkProps } from '../../links';
import { useAppNavLinks } from '../nav_links';
import { useNavLinks } from '../../../links/nav_links';
import { useShowTimeline } from '../../../utils/timeline/use_show_timeline';
import { useIsPolicySettingsBarVisible } from '../../../../management/pages/policy/view/policy_hooks';
import { track } from '../../../lib/telemetry';
Expand All @@ -30,7 +30,7 @@ const isGetStartedNavItem = (id: SecurityPageName) => id === SecurityPageName.la
* Returns the formatted `items` and `footerItems` to be rendered in the navigation
*/
const useSolutionSideNavItems = () => {
const navLinks = useAppNavLinks();
const navLinks = useNavLinks();
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); // adds href and onClick props

const sideNavItems = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
* 2.0.
*/

import type { IconType } from '@elastic/eui';
import { SecurityPageName } from '../../../app/types';
import type { LinkCategories } from '../../links';

export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean };

Expand Down Expand Up @@ -101,18 +99,3 @@ export interface SecuritySolutionTabNavigationProps {
}

export type NavigateToUrl = (url: string) => void;
export interface NavLinkItem {
categories?: LinkCategories;
description?: string;
disabled?: boolean;
icon?: IconType;
id: SecurityPageName;
links?: NavLinkItem[];
image?: string;
title: string;
skipUrlState?: boolean;
isBeta?: boolean;
betaOptions?: {
text: string;
};
}
96 changes: 36 additions & 60 deletions x-pack/plugins/security_solution/public/common/links/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

import type { Capabilities } from '@kbn/core/public';
import { get, isArray } from 'lodash';
import { useEffect, useState } from 'react';
import { useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { BehaviorSubject } from 'rxjs';
import type { SecurityPageName } from '../../../common/constants';
import type {
Expand All @@ -20,72 +21,48 @@ import type {
} from './types';

/**
* App links updater, it keeps the value of the app links in sync with all application.
* It can be updated using `updateAppLinks` or `excludeAppLink`
* Read it using `subscribeAppLinks` or `useAppLinks` hook.
* App links updater, it stores the `appLinkItems` recursive hierarchy and keeps
* the value of the app links in sync with all application components.
* It can be updated using `updateAppLinks`.
* Read it using subscription or `useAppLinks` hook.
*/
const appLinksUpdater$ = new BehaviorSubject<{
links: AppLinkItems;
normalizedLinks: NormalizedLinks;
}>({
links: [], // stores the appLinkItems recursive hierarchy
normalizedLinks: {}, // stores a flatten normalized object for direct id access
});
const appLinksUpdater$ = new BehaviorSubject<AppLinkItems>([]);
// stores a flatten normalized appLinkItems object for internal direct id access
const normalizedAppLinksUpdater$ = new BehaviorSubject<NormalizedLinks>({});

const getAppLinksValue = (): AppLinkItems => appLinksUpdater$.getValue().links;
const getNormalizedLinksValue = (): NormalizedLinks => appLinksUpdater$.getValue().normalizedLinks;
// AppLinks observable
export const appLinks$ = appLinksUpdater$.asObservable();

/**
* Subscribes to the updater to get the app links updates
* Updates the app links applying the filter by permissions
*/
export const subscribeAppLinks = (onChange: (links: AppLinkItems) => void) =>
appLinksUpdater$.subscribe(({ links }) => onChange(links));
export const updateAppLinks = (
appLinksToUpdate: AppLinkItems,
linksPermissions: LinksPermissions
) => {
const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
appLinksUpdater$.next(Object.freeze(filteredAppLinks));
normalizedAppLinksUpdater$.next(Object.freeze(getNormalizedLinks(filteredAppLinks)));
};

/**
* Hook to get the app links updated value
*/
export const useAppLinks = (): AppLinkItems => {
const [appLinks, setAppLinks] = useState(getAppLinksValue);

useEffect(() => {
const linksSubscription = subscribeAppLinks((newAppLinks) => {
setAppLinks(newAppLinks);
});
return () => linksSubscription.unsubscribe();
}, []);

return appLinks;
};
export const useAppLinks = (): AppLinkItems =>
useObservable(appLinksUpdater$, appLinksUpdater$.getValue());
/**
* Hook to get the normalized app links updated value
*/
export const useNormalizedAppLinks = (): NormalizedLinks =>
useObservable(normalizedAppLinksUpdater$, normalizedAppLinksUpdater$.getValue());

/**
* Hook to check if a link exists in the application links,
* It can be used to know if a link access is authorized.
*/
export const useLinkExists = (id: SecurityPageName): boolean => {
const [linkExists, setLinkExists] = useState(!!getNormalizedLink(id));

useEffect(() => {
const linksSubscription = subscribeAppLinks(() => {
setLinkExists(!!getNormalizedLink(id));
});
return () => linksSubscription.unsubscribe();
}, [id]);

return linkExists;
};

/**
* Updates the app links applying the filter by permissions
*/
export const updateAppLinks = (
appLinksToUpdate: AppLinkItems,
linksPermissions: LinksPermissions
) => {
const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
appLinksUpdater$.next({
links: Object.freeze(filteredAppLinks),
normalizedLinks: Object.freeze(getNormalizedLinks(filteredAppLinks)),
});
const normalizedLinks = useNormalizedAppLinks();
return useMemo(() => !!normalizedLinks[id], [normalizedLinks, id]);
};

/**
Expand Down Expand Up @@ -128,6 +105,10 @@ export const needsUrlState = (id: SecurityPageName): boolean => {
return !getNormalizedLink(id)?.skipUrlState;
};

export const getLinksWithHiddenTimeline = (): LinkInfo[] => {
return Object.values(normalizedAppLinksUpdater$.getValue()).filter((link) => link.hideTimeline);
};

// Internal functions

/**
Expand All @@ -136,8 +117,8 @@ export const needsUrlState = (id: SecurityPageName): boolean => {
const getNormalizedLinks = (
currentLinks: AppLinkItems,
parentId?: SecurityPageName
): NormalizedLinks => {
return currentLinks.reduce<NormalizedLinks>((normalized, { links, ...currentLink }) => {
): NormalizedLinks =>
currentLinks.reduce<NormalizedLinks>((normalized, { links, ...currentLink }) => {
normalized[currentLink.id] = {
...currentLink,
parentId,
Expand All @@ -147,10 +128,9 @@ const getNormalizedLinks = (
}
return normalized;
}, {});
};

const getNormalizedLink = (id: SecurityPageName): Readonly<NormalizedLink> | undefined =>
getNormalizedLinksValue()[id];
normalizedAppLinksUpdater$.getValue()[id];

const getFilteredAppLinks = (
appLinkToFilter: AppLinkItems,
Expand Down Expand Up @@ -226,7 +206,3 @@ const isLinkAllowed = (
}
return true;
};

export const getLinksWithHiddenTimeline = (): LinkInfo[] => {
return Object.values(getNormalizedLinksValue()).filter((link) => link.hideTimeline);
};
Loading