Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ApplicationLinksUpdateParams {
class ApplicationLinksUpdater {
private readonly linksSubject$ = new BehaviorSubject<AppLinkItems>([]);
private readonly normalizedLinksSubject$ = new BehaviorSubject<NormalizedLinks>({});
private lastUpdateParams?: ApplicationLinksUpdateParams | undefined;

/** Observable that stores the links recursive hierarchy */
public readonly links$: Observable<AppLinkItems>;
Expand All @@ -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<LinkItem>) {
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<LinkItem>,
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;
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<Omit<LinkInfo, 'id'>>) => void;
export const useUpdateLinkConfig = (): UpdateLinkConfig => {
const updateLinkConfig = useMemo<UpdateLinkConfig>(
() =>
// make sure to only update if the update is different, this is important to avoid unnecessary updates
memoizeOne<UpdateLinkConfig>((id, update) => {
applicationLinksUpdater.updateAppLink(id, update);
}, isEqual), // deep equality check
[]
);
return updateLinkConfig;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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);
`;
Expand Down