diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/links/application_links_updater.ts b/x-pack/solutions/security/plugins/security_solution/public/app/links/application_links_updater.ts index b8dcd0964ba56..6cac179578ee9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/links/application_links_updater.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/links/application_links_updater.ts @@ -36,6 +36,7 @@ export interface ApplicationLinksUpdateParams { class ApplicationLinksUpdater { private readonly linksSubject$ = new BehaviorSubject([]); private readonly normalizedLinksSubject$ = new BehaviorSubject({}); + private lastUpdateParams?: ApplicationLinksUpdateParams | undefined; /** Observable that stores the links recursive hierarchy */ public readonly links$: Observable; @@ -51,23 +52,47 @@ class ApplicationLinksUpdater { * Updates the internal app links applying the filter by permissions */ public update(appLinksToUpdate: AppLinkItems, params: ApplicationLinksUpdateParams) { + this.lastUpdateParams = params; const processedAppLinks = this.processAppLinks(appLinksToUpdate, params); this.linksSubject$.next(Object.freeze(processedAppLinks)); this.normalizedLinksSubject$.next(Object.freeze(this.getNormalizedLinks(processedAppLinks))); } /** - * Returns the current links value + * Returns the current normalized links value */ - public getLinksValue(): AppLinkItems { - return this.linksSubject$.getValue(); + public getNormalizedLinksValue(): NormalizedLinks { + return this.normalizedLinksSubject$.getValue(); } /** - * Returns the current normalized links value + * Updates a specific app link by its `SecurityPageName` identifier. */ - public getNormalizedLinksValue(): NormalizedLinks { - return this.normalizedLinksSubject$.getValue(); + public updateAppLink(id: SecurityPageName, appLink: Partial) { + if (!this.lastUpdateParams) { + throw new Error( + 'Cannot update app link without previous update params. Please call `update` method first.' + ); + } + const currentLinks = this.linksSubject$.getValue(); + const updatedLinks = this.getUpdatedAppLink(id, appLink, currentLinks); + this.update(updatedLinks, this.lastUpdateParams); + } + + private getUpdatedAppLink( + id: SecurityPageName, + appLinkUpdate: Partial, + currentLinks: AppLinkItems + ): LinkItem[] { + return currentLinks.map((link) => { + if (link.id === id) { + return { ...link, ...appLinkUpdate }; + } + if (link.links) { + return { ...link, links: this.getUpdatedAppLink(id, appLinkUpdate, link.links) }; + } + return link; + }); } /** diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/links/links_hooks.ts b/x-pack/solutions/security/plugins/security_solution/public/common/links/links_hooks.ts index 5c3e081e88fe3..32b8fab0d478b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/links/links_hooks.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/links/links_hooks.ts @@ -7,6 +7,8 @@ import { useMemo, useCallback } from 'react'; import useObservable from 'react-use/lib/useObservable'; import type { SecurityPageName } from '@kbn/security-solution-navigation'; +import memoizeOne from 'memoize-one'; +import { isEqual } from 'lodash'; import type { LinkInfo, NormalizedLink, NormalizedLinks } from './types'; import { applicationLinksUpdater } from '../../app/links/application_links_updater'; @@ -78,3 +80,22 @@ export const useParentLinks = (id: SecurityPageName): LinkInfo[] => { return ancestors.reverse(); }, [normalizedLinks, id]); }; + +/** + * Hook that returns a function to update a specific link configuration. + * It takes the `SecurityPageName` id and a partial `LinkInfo` object to update the link. + * The function will update the links observable and trigger a re-render of components that depend on the links. + * Use with caution, as it will also trigger an update of the plugin deepLinks configuration as well. + */ +type UpdateLinkConfig = (id: SecurityPageName, update: Partial>) => void; +export const useUpdateLinkConfig = (): UpdateLinkConfig => { + const updateLinkConfig = useMemo( + () => + // make sure to only update if the update is different, this is important to avoid unnecessary updates + memoizeOne((id, update) => { + applicationLinksUpdater.updateAppLink(id, update); + }, isEqual), // deep equality check + [] + ); + return updateLinkConfig; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx index 7d1dc659e3519..194215e467d9e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx @@ -39,6 +39,7 @@ import { usePrivilegedMonitoringEngineStatus } from '../api/hooks/use_privileged import { PrivilegedUserMonitoringManageDataSources } from '../components/privileged_user_monitoring_manage_data_sources'; import { EmptyPrompt } from '../../common/components/empty_prompt'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; +import { useLinkInfo, useUpdateLinkConfig } from '../../common/links/links_hooks'; import { PageLoader } from '../../common/components/page_loader'; type PageState = @@ -177,6 +178,25 @@ export const EntityAnalyticsPrivilegedUserMonitoringPage = () => { engineStatus.isLoading, ]); + const linkInfo = useLinkInfo(SecurityPageName.entityAnalyticsPrivilegedUserMonitoring); + const updateLinkConfig = useUpdateLinkConfig(); + + // Update UrlParam to add hideTimeline to the URL when the onboarding is loaded and removes it when dashboard is loaded + useEffect(() => { + // do not change the link config when the engine status is being fetched + if (state.type === 'fetchingEngineStatus') { + return; + } + + const hideTimeline = ['onboarding', 'initializingEngine'].includes(state.type); + // update the hideTimeline property in the link config. This call triggers expensive operations, use with love + const hideTimelineConfig = linkInfo?.hideTimeline ?? false; + + if (hideTimeline !== hideTimelineConfig) { + updateLinkConfig(SecurityPageName.entityAnalyticsPrivilegedUserMonitoring, { hideTimeline }); + } + }, [linkInfo?.hideTimeline, state.type, updateLinkConfig]); + const fullHeightCSS = css` min-height: calc(100vh - 240px); `;