Skip to content

Commit 38f7f84

Browse files
committed
Unify available data, license & feature checks in a single flag
1 parent e6c79e7 commit 38f7f84

File tree

5 files changed

+84
-50
lines changed

5 files changed

+84
-50
lines changed

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/tabs/visualize_tab.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
uiMetricService,
1616
GRAPH_INVESTIGATION,
1717
} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
18-
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
1918
import { useDocumentDetailsContext } from '../../shared/context';
2019
import {
2120
VISUALIZE_TAB_BUTTON_GROUP_TEST_ID,
@@ -30,7 +29,6 @@ import { useStartTransaction } from '../../../../common/lib/apm/use_start_transa
3029
import { GRAPH_ID, GraphVisualization } from '../components/graph_visualization';
3130
import { useGraphPreview } from '../../shared/hooks/use_graph_preview';
3231
import { METRIC_TYPE } from '../../../../common/lib/telemetry';
33-
import { ENABLE_GRAPH_VISUALIZATION_SETTING } from '../../../../../common/constants';
3432

3533
const visualizeButtons: EuiButtonGroupOptionProps[] = [
3634
{
@@ -113,20 +111,18 @@ export const VisualizeTab = memo(() => {
113111
dataFormattedForFieldBrowser,
114112
});
115113

116-
const [graphVisualizationEnabled] = useUiSetting$<boolean>(ENABLE_GRAPH_VISUALIZATION_SETTING);
117-
118114
const options = [...visualizeButtons];
119115

120-
if (hasGraphRepresentation && graphVisualizationEnabled) {
116+
if (hasGraphRepresentation) {
121117
options.push(graphVisualizationButton);
122118
}
123119

124120
useEffect(() => {
125121
if (panels.left?.path?.subTab) {
126122
const newId = panels.left.path.subTab;
127123

128-
// Check if we need to select a different tab when graph feature flag is disabled
129-
if (newId === GRAPH_ID && hasGraphRepresentation && !graphVisualizationEnabled) {
124+
// Check if we need to select a different tab when graph is not available
125+
if (newId === GRAPH_ID && !hasGraphRepresentation) {
130126
setActiveVisualizationId(SESSION_VIEW_ID);
131127
} else {
132128
setActiveVisualizationId(newId);
@@ -136,7 +132,7 @@ export const VisualizeTab = memo(() => {
136132
}
137133
}
138134
}
139-
}, [panels.left?.path?.subTab, graphVisualizationEnabled, hasGraphRepresentation]);
135+
}, [panels.left?.path?.subTab, hasGraphRepresentation]);
140136

141137
return (
142138
<>

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ describe('<VisualizationsSection />', () => {
113113
alertIds: undefined,
114114
statsNodes: undefined,
115115
});
116+
// Default mock: graph visualization not available (UI setting is false by default)
116117
mockUseGraphPreview.mockReturnValue({
117-
hasGraphRepresentation: true,
118+
hasGraphRepresentation: false,
118119
eventIds: [],
119120
});
120121
mockUseFetchGraphData.mockReturnValue({
@@ -178,6 +179,12 @@ describe('<VisualizationsSection />', () => {
178179
return useUiSetting$Mock(key, defaultValue);
179180
});
180181

182+
// Mock useGraphPreview to reflect that graph is available when UI setting is enabled
183+
mockUseGraphPreview.mockReturnValue({
184+
hasGraphRepresentation: true,
185+
eventIds: [],
186+
});
187+
181188
const { getByTestId } = renderVisualizationsSection();
182189

183190
expect(getByTestId(`${GRAPH_PREVIEW_TEST_ID}LeftSection`)).toBeInTheDocument();

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
* 2.0.
66
*/
77

8-
import React, { memo, useMemo } from 'react';
8+
import React, { memo } from 'react';
99
import { EuiSpacer } from '@elastic/eui';
1010
import { FormattedMessage } from '@kbn/i18n-react';
11-
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
1211
import { useExpandSection } from '../hooks/use_expand_section';
1312
import { AnalyzerPreviewContainer } from './analyzer_preview_container';
1413
import { SessionPreviewContainer } from './session_preview_container';
@@ -17,7 +16,6 @@ import { VISUALIZATIONS_TEST_ID } from './test_ids';
1716
import { GraphPreviewContainer } from './graph_preview_container';
1817
import { useDocumentDetailsContext } from '../../shared/context';
1918
import { useGraphPreview } from '../../shared/hooks/use_graph_preview';
20-
import { ENABLE_GRAPH_VISUALIZATION_SETTING } from '../../../../../common/constants';
2119

2220
const KEY = 'visualizations';
2321

@@ -29,20 +27,13 @@ export const VisualizationsSection = memo(() => {
2927
const { dataAsNestedObject, getFieldsData, dataFormattedForFieldBrowser } =
3028
useDocumentDetailsContext();
3129

32-
const [graphVisualizationEnabled] = useUiSetting$<boolean>(ENABLE_GRAPH_VISUALIZATION_SETTING);
33-
3430
// Decide whether to show the graph preview or not
3531
const { hasGraphRepresentation } = useGraphPreview({
3632
getFieldsData,
3733
ecsData: dataAsNestedObject,
3834
dataFormattedForFieldBrowser,
3935
});
4036

41-
const shouldShowGraphPreview = useMemo(
42-
() => graphVisualizationEnabled && hasGraphRepresentation,
43-
[graphVisualizationEnabled, hasGraphRepresentation]
44-
);
45-
4637
return (
4738
<ExpandableSection
4839
expanded={expanded}
@@ -58,7 +49,7 @@ export const VisualizationsSection = memo(() => {
5849
<SessionPreviewContainer />
5950
<EuiSpacer />
6051
<AnalyzerPreviewContainer />
61-
{shouldShowGraphPreview && (
52+
{hasGraphRepresentation && (
6253
<>
6354
<EuiSpacer />
6455
<GraphPreviewContainer />

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_graph_preview.test.tsx

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { useHasGraphVisualizationAccess } from '../../../../common/hooks/use_has
1717
jest.mock('../../../../common/hooks/use_has_graph_visualization_access');
1818
const mockUseHasGraphVisualizationAccess = useHasGraphVisualizationAccess as jest.Mock;
1919

20+
jest.mock('@kbn/kibana-react-plugin/public');
21+
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
22+
const mockUseUiSetting = useUiSetting$ as jest.Mock;
23+
2024
const alertMockGetFieldsData: GetFieldsData = (field: string) => {
2125
if (field === 'kibana.alert.uuid') {
2226
return 'alertId';
@@ -56,6 +60,8 @@ describe('useGraphPreview', () => {
5660
jest.clearAllMocks();
5761
// Default mock: graph visualization feature is available
5862
mockUseHasGraphVisualizationAccess.mockReturnValue(true);
63+
// Default mock: UI setting is enabled
64+
mockUseUiSetting.mockReturnValue([true, jest.fn()]);
5965
});
6066
it(`should return false when missing actor`, () => {
6167
const getFieldsData: GetFieldsData = (field: string) => {
@@ -336,33 +342,50 @@ describe('useGraphPreview', () => {
336342
});
337343
});
338344

339-
describe('License checking', () => {
340-
it('should return false when all conditions are met but env does not have required license', () => {
341-
mockUseHasGraphVisualizationAccess.mockReturnValue(false);
342-
343-
const hookResult = renderHook((props: UseGraphPreviewParams) => useGraphPreview(props), {
344-
initialProps: {
345-
getFieldsData: alertMockGetFieldsData,
346-
ecsData: {
347-
_id: 'id',
348-
event: {
349-
action: ['action'],
350-
},
345+
it('should return false when all conditions are met but env does not have required license', () => {
346+
mockUseHasGraphVisualizationAccess.mockReturnValue(false);
347+
348+
const hookResult = renderHook((props: UseGraphPreviewParams) => useGraphPreview(props), {
349+
initialProps: {
350+
getFieldsData: alertMockGetFieldsData,
351+
ecsData: {
352+
_id: 'id',
353+
event: {
354+
action: ['action'],
351355
},
352-
dataFormattedForFieldBrowser: alertMockDataFormattedForFieldBrowser,
353356
},
354-
});
355-
356-
expect(hookResult.result.current.hasGraphRepresentation).toBe(false);
357-
expect(hookResult.result.current).toStrictEqual({
358-
hasGraphRepresentation: false,
359-
timestamp: mockFieldData['@timestamp'][0],
360-
eventIds: ['eventId'],
361-
actorIds: ['actorId'],
362-
action: ['action'],
363-
targetIds: ['targetId'],
364-
isAlert: true,
365-
});
357+
dataFormattedForFieldBrowser: alertMockDataFormattedForFieldBrowser,
358+
},
366359
});
360+
361+
expect(hookResult.result.current.hasGraphRepresentation).toBe(false);
362+
expect(hookResult.result.current).toStrictEqual({
363+
hasGraphRepresentation: false,
364+
timestamp: mockFieldData['@timestamp'][0],
365+
eventIds: ['eventId'],
366+
actorIds: ['actorId'],
367+
action: ['action'],
368+
targetIds: ['targetId'],
369+
isAlert: true,
370+
});
371+
});
372+
373+
it('should return false for hasGraphRepresentation when UI setting is disabled', () => {
374+
mockUseUiSetting.mockReturnValue([false, jest.fn()]);
375+
376+
const hookResult = renderHook((props: UseGraphPreviewParams) => useGraphPreview(props), {
377+
initialProps: {
378+
getFieldsData: alertMockGetFieldsData,
379+
ecsData: {
380+
_id: 'id',
381+
event: {
382+
action: ['action'],
383+
},
384+
},
385+
dataFormattedForFieldBrowser: alertMockDataFormattedForFieldBrowser,
386+
},
387+
});
388+
389+
expect(hookResult.result.current.hasGraphRepresentation).toBe(false);
367390
});
368391
});

x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/hooks/use_graph_preview.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
99
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
1010
import { get } from 'lodash/fp';
11+
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
1112
import type { GetFieldsData } from './use_get_fields_data';
1213
import { getField, getFieldArray } from '../utils';
1314
import { useBasicDataFromDetailsData } from './use_basic_data_from_details_data';
1415
import { useHasGraphVisualizationAccess } from '../../../../common/hooks/use_has_graph_visualization_access';
16+
import { ENABLE_GRAPH_VISUALIZATION_SETTING } from '../../../../../common/constants';
1517

1618
export interface UseGraphPreviewParams {
1719
/**
@@ -59,7 +61,8 @@ export interface UseGraphPreviewResult {
5961
action?: string[];
6062

6163
/**
62-
* Boolean indicating if the event has a graph representation (contains event ids, actor ids, action, and valid license)
64+
* Boolean indicating if graph visualization is fully available
65+
* Combines: hasGraphData + valid license + feature enabled in settings
6366
*/
6467
hasGraphRepresentation: boolean;
6568

@@ -91,15 +94,29 @@ export const useGraphPreview = ({
9194
// Serverless: Security Analytics Complete tier (via PLI feature keys)
9295
const hasRequiredLicense = useHasGraphVisualizationAccess();
9396

94-
const hasGraphRepresentation =
97+
// Check if graph visualization feature is enabled in UI settings
98+
const [isGraphFeatureEnabled] = useUiSetting$<boolean>(ENABLE_GRAPH_VISUALIZATION_SETTING);
99+
100+
// Check if event has all required data fields for graph visualization
101+
const hasGraphData =
95102
Boolean(timestamp) &&
96103
Boolean(action?.length) &&
97104
actorIds.length > 0 &&
98105
eventIds.length > 0 &&
99-
targetIds.length > 0 &&
100-
hasRequiredLicense;
106+
targetIds.length > 0;
107+
108+
// Combine all conditions: data availability + license + feature flag
109+
const hasGraphRepresentation = hasGraphData && hasRequiredLicense && isGraphFeatureEnabled;
101110

102111
const { isAlert } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
103112

104-
return { timestamp, eventIds, actorIds, action, targetIds, hasGraphRepresentation, isAlert };
113+
return {
114+
timestamp,
115+
eventIds,
116+
actorIds,
117+
action,
118+
targetIds,
119+
hasGraphRepresentation,
120+
isAlert,
121+
};
105122
};

0 commit comments

Comments
 (0)