Skip to content

Commit 0e9c9c9

Browse files
authored
[Security Solution][Entity Analytics] Change hide timeline approach in priv mon onboarding (#229096)
## Summary Remove the hook to update link config, in favour of the CSS approach. ### Why As discussed with @michaelolo24 and @kqualters-elastic, the `useUpdateLinkConfig` is too generic and too powerful. Every time the function is called, the Security Solution plugin registry updater is called to change the `deepLinks`, and it re-mounts the app. It's very prone to misuse, and we don't want to open that door, especially if it's created only to solve this small visual issue with the timeline. ### Alternative The "patchy but simple" approach proposed by @kqualters-elastic, which hides the timeline updating the `element.style.display`, has been applied. A `setTimeout` has been introduced to solve the problem of the timeline not being rendered yet when the effect is executed. @machadoum Please let me know what you think. ## Demo https://github.com/user-attachments/assets/0347fddb-07bd-43f8-8899-6a10a9b2efd0 (The update of the status from "onboarding" to "dashboard" has been artificially simulated for this demo.)
1 parent 9c18356 commit 0e9c9c9

File tree

4 files changed

+42
-70
lines changed

4 files changed

+42
-70
lines changed

x-pack/solutions/security/plugins/security_solution/public/app/links/application_links_updater.ts

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export interface ApplicationLinksUpdateParams {
3636
class ApplicationLinksUpdater {
3737
private readonly linksSubject$ = new BehaviorSubject<AppLinkItems>([]);
3838
private readonly normalizedLinksSubject$ = new BehaviorSubject<NormalizedLinks>({});
39-
private lastUpdateParams?: ApplicationLinksUpdateParams | undefined;
4039

4140
/** Observable that stores the links recursive hierarchy */
4241
public readonly links$: Observable<AppLinkItems>;
@@ -52,7 +51,6 @@ class ApplicationLinksUpdater {
5251
* Updates the internal app links applying the filter by permissions
5352
*/
5453
public update(appLinksToUpdate: AppLinkItems, params: ApplicationLinksUpdateParams) {
55-
this.lastUpdateParams = params;
5654
const processedAppLinks = this.processAppLinks(appLinksToUpdate, params);
5755
this.linksSubject$.next(Object.freeze(processedAppLinks));
5856
this.normalizedLinksSubject$.next(Object.freeze(this.getNormalizedLinks(processedAppLinks)));
@@ -65,36 +63,6 @@ class ApplicationLinksUpdater {
6563
return this.normalizedLinksSubject$.getValue();
6664
}
6765

68-
/**
69-
* Updates a specific app link by its `SecurityPageName` identifier.
70-
*/
71-
public updateAppLink(id: SecurityPageName, appLink: Partial<LinkItem>) {
72-
if (!this.lastUpdateParams) {
73-
throw new Error(
74-
'Cannot update app link without previous update params. Please call `update` method first.'
75-
);
76-
}
77-
const currentLinks = this.linksSubject$.getValue();
78-
const updatedLinks = this.getUpdatedAppLink(id, appLink, currentLinks);
79-
this.update(updatedLinks, this.lastUpdateParams);
80-
}
81-
82-
private getUpdatedAppLink(
83-
id: SecurityPageName,
84-
appLinkUpdate: Partial<LinkItem>,
85-
currentLinks: AppLinkItems
86-
): LinkItem[] {
87-
return currentLinks.map((link) => {
88-
if (link.id === id) {
89-
return { ...link, ...appLinkUpdate };
90-
}
91-
if (link.links) {
92-
return { ...link, links: this.getUpdatedAppLink(id, appLinkUpdate, link.links) };
93-
}
94-
return link;
95-
});
96-
}
97-
9866
/**
9967
* Creates the `NormalizedLinks` structure from a `LinkItem` array
10068
*/

x-pack/solutions/security/plugins/security_solution/public/common/links/links_hooks.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import { useMemo, useCallback } from 'react';
88
import useObservable from 'react-use/lib/useObservable';
99
import type { SecurityPageName } from '@kbn/security-solution-navigation';
10-
import memoizeOne from 'memoize-one';
11-
import { isEqual } from 'lodash';
1210
import type { LinkInfo, NormalizedLink, NormalizedLinks } from './types';
1311
import { applicationLinksUpdater } from '../../app/links/application_links_updater';
1412

@@ -80,22 +78,3 @@ export const useParentLinks = (id: SecurityPageName): LinkInfo[] => {
8078
return ancestors.reverse();
8179
}, [normalizedLinks, id]);
8280
};
83-
84-
/**
85-
* Hook that returns a function to update a specific link configuration.
86-
* It takes the `SecurityPageName` id and a partial `LinkInfo` object to update the link.
87-
* The function will update the links observable and trigger a re-render of components that depend on the links.
88-
* Use with caution, as it will also trigger an update of the plugin deepLinks configuration as well.
89-
*/
90-
type UpdateLinkConfig = (id: SecurityPageName, update: Partial<Omit<LinkInfo, 'id'>>) => void;
91-
export const useUpdateLinkConfig = (): UpdateLinkConfig => {
92-
const updateLinkConfig = useMemo<UpdateLinkConfig>(
93-
() =>
94-
// make sure to only update if the update is different, this is important to avoid unnecessary updates
95-
memoizeOne<UpdateLinkConfig>((id, update) => {
96-
applicationLinksUpdater.updateAppLink(id, update);
97-
}, isEqual), // deep equality check
98-
[]
99-
);
100-
return updateLinkConfig;
101-
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
const TIMELINE_SELECTOR = '[data-test-subj="timeline-bottom-bar-container"]';
9+
10+
/**
11+
* A utility function to forcefully hide the timeline bottom bar.
12+
* This is useful for scenarios where the timeline should not be visible, such as during onboarding or initialization.
13+
* @param hidden Whether the timeline should be forced hidden or have the default visibility.
14+
*/
15+
export const forceHiddenTimeline = (hidden: boolean) => {
16+
let element: HTMLElement | null = null;
17+
const timeout = setTimeout(() => {
18+
// Use a timeout to ensure the element is available in the DOM
19+
// This is necessary because the element might not be rendered immediately
20+
element = document.querySelector(TIMELINE_SELECTOR);
21+
if (element) {
22+
if (hidden) {
23+
element.style.display = 'none';
24+
} else {
25+
element.style.removeProperty('display');
26+
}
27+
}
28+
});
29+
30+
// Return the cleanup function to clear the timeout
31+
return () => {
32+
clearTimeout(timeout);
33+
if (element) {
34+
element.style.removeProperty('display');
35+
}
36+
};
37+
};

x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ import { usePrivilegedMonitoringEngineStatus } from '../api/hooks/use_privileged
3939
import { PrivilegedUserMonitoringManageDataSources } from '../components/privileged_user_monitoring_manage_data_sources';
4040
import { EmptyPrompt } from '../../common/components/empty_prompt';
4141
import { useDataView } from '../../data_view_manager/hooks/use_data_view';
42-
import { useLinkInfo, useUpdateLinkConfig } from '../../common/links/links_hooks';
4342
import { PageLoader } from '../../common/components/page_loader';
4443
import { DataViewManagerScopeName } from '../../data_view_manager/constants';
44+
import { forceHiddenTimeline } from '../../common/utils/timeline/force_hidden_timeline';
4545

4646
type PageState =
4747
| { type: 'fetchingEngineStatus' }
@@ -179,24 +179,12 @@ export const EntityAnalyticsPrivilegedUserMonitoringPage = () => {
179179
engineStatus.isLoading,
180180
]);
181181

182-
const linkInfo = useLinkInfo(SecurityPageName.entityAnalyticsPrivilegedUserMonitoring);
183-
const updateLinkConfig = useUpdateLinkConfig();
184-
185-
// Update UrlParam to add hideTimeline to the URL when the onboarding is loaded and removes it when dashboard is loaded
182+
// Hide the timeline bottom bar when the page is in onboarding or initializing state
186183
useEffect(() => {
187-
// do not change the link config when the engine status is being fetched
188-
if (state.type === 'fetchingEngineStatus') {
189-
return;
190-
}
191-
192184
const hideTimeline = ['onboarding', 'initializingEngine'].includes(state.type);
193-
// update the hideTimeline property in the link config. This call triggers expensive operations, use with love
194-
const hideTimelineConfig = linkInfo?.hideTimeline ?? false;
195-
196-
if (hideTimeline !== hideTimelineConfig) {
197-
updateLinkConfig(SecurityPageName.entityAnalyticsPrivilegedUserMonitoring, { hideTimeline });
198-
}
199-
}, [linkInfo?.hideTimeline, state.type, updateLinkConfig]);
185+
const cleanup = forceHiddenTimeline(hideTimeline);
186+
return cleanup;
187+
}, [state.type]);
200188

201189
const fullHeightCSS = css`
202190
min-height: calc(100vh - 240px);

0 commit comments

Comments
 (0)