Skip to content

Commit

Permalink
Merge pull request #30 from semd/security_serverless_navigation_poc
Browse files Browse the repository at this point in the history
[PoC][Security Solution] Serverless navigation
  • Loading branch information
clintandrewhall authored Mar 15, 2023
2 parents 3a407f3 + fd091dc commit 382607f
Show file tree
Hide file tree
Showing 25 changed files with 517 additions and 256 deletions.
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

0 comments on commit 382607f

Please sign in to comment.