Skip to content

Commit 3ea4a24

Browse files
albertoblazkfirpeled
authored andcommitted
[Cloud Security] [Graph Vis] Implement UI enhancements (elastic#222830)
## Summary Closes: - elastic#222367 Enhance graph visualization UI with latest Figma update. ### Screenshots <details><summary>Popover</summary> <img width="370" alt="Screenshot 2025-06-12 at 10 21 50" src="https://github.com/user-attachments/assets/8060860e-5b93-4d71-b330-1920afb75c6a" /> </details> <details><summary>Controls</summary> <img width="63" alt="Screenshot 2025-06-11 at 17 36 27" src="https://github.com/user-attachments/assets/78b93528-1821-4d77-9536-eb88fd68e3dc" /> </details> <details><summary>Edges - new color, default to solid stroke, no start marker</summary> <img width="1078" alt="Screenshot 2025-06-12 at 17 22 25" src="https://github.com/user-attachments/assets/53b46adb-2b79-4c65-ba48-9c74826f2fb0" /> </details> ### Videos #### Snap nodes to 10px grid https://github.com/user-attachments/assets/fc732784-1e3b-4277-9bf3-d7a6c9b43f88 #### Zoom / Fit to view transition https://github.com/user-attachments/assets/8a7627c0-7c00-4321-a05c-ea9fa1910002 ### Definition of done - [x] Update popover container - [x] Update popover action icons - [x] Update popover action texts - [x] Update fit-to-view icons - [x] Update relationship arrow colors - [x] Check if we can increase the current nodes limit - increased to 300 - [x] Snap to Grid: Enable nodes to snap by 10px - [x] Implement smooth zoom and fit-to-view transitions (200ms duration) - [x] Remove edge's start marker - [x] Remove failure representation according to `event.outcome` ### How to test - In Kibana 1. Add this line to your `kibana.dev.yml`: ```yml uiSettings.overrides.securitySolution:enableGraphVisualization: true ``` 2. Then, run these 2 commands while running Kibana with `yarn start --no-base-path`. This is for setting up the local env with data. ```bash node scripts/es_archiver load x-pack/solutions/security/test/cloud_security_posture_functional/es_archives/logs_gcp_audit --es-url http://elastic:changeme@localhost:9200 --kibana-url http://elastic:changeme@localhost:5601 ``` ```bash node scripts/es_archiver load x-pack/solutions/security/test/cloud_security_posture_functional/es_archives/security_alerts --es-url http://elastic:changeme@localhost:9200 --kibana-url http://elastic:changeme@localhost:5601 ``` 3. Finally in Kibana, go to Alerts and update the date-picker to include data from a year ago. Then check one of the alerts details opening the right-side flyout and find the "Graph preview" section in it. ### How to test - In Storybook 1. Run in terminal: ```bash yarn storybook cloud_security_posture_graph ``` 2. Open [http://localhost:9001/](http://localhost:9001/). ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks No risk, feature is gated under the `securitySolution:enableGraphVisualization` UI setting. --------- Co-authored-by: Kfir Peled <[email protected]> Co-authored-by: Kfir Peled <[email protected]>
1 parent 93ea535 commit 3ea4a24

File tree

30 files changed

+452
-268
lines changed

30 files changed

+452
-268
lines changed

x-pack/platform/packages/shared/kbn-cloud-security-posture/common/schema/graph/v1.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,19 @@ export const graphResponseSchema = () =>
4242
),
4343
});
4444

45-
export const colorSchema = schema.oneOf([
45+
export const nodeColorSchema = schema.oneOf([
4646
schema.literal('primary'),
4747
schema.literal('danger'),
4848
schema.literal('warning'),
4949
]);
5050

51+
export const edgeColorSchema = schema.oneOf([
52+
schema.literal('primary'),
53+
schema.literal('danger'),
54+
schema.literal('warning'),
55+
schema.literal('subdued'),
56+
]);
57+
5158
export const nodeShapeSchema = schema.oneOf([
5259
schema.literal('hexagon'),
5360
schema.literal('pentagon'),
@@ -67,7 +74,7 @@ export const nodeBaseDataSchema = schema.object({
6774
export const entityNodeDataSchema = schema.allOf([
6875
nodeBaseDataSchema,
6976
schema.object({
70-
color: colorSchema,
77+
color: nodeColorSchema,
7178
shape: schema.oneOf([
7279
schema.literal('hexagon'),
7380
schema.literal('pentagon'),
@@ -90,14 +97,14 @@ export const labelNodeDataSchema = schema.allOf([
9097
schema.object({
9198
shape: schema.literal('label'),
9299
parentId: schema.maybe(schema.string()),
93-
color: colorSchema,
100+
color: nodeColorSchema,
94101
}),
95102
]);
96103

97104
export const edgeDataSchema = schema.object({
98105
id: schema.string(),
99106
source: schema.string(),
100107
target: schema.string(),
101-
color: colorSchema,
108+
color: edgeColorSchema,
102109
type: schema.maybe(schema.oneOf([schema.literal('solid'), schema.literal('dashed')])),
103110
});

x-pack/platform/packages/shared/kbn-cloud-security-posture/common/types/graph/v1.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
import type { TypeOf } from '@kbn/config-schema';
99
import type { BoolQuery } from '@kbn/es-query';
1010
import {
11-
colorSchema,
11+
edgeColorSchema,
1212
edgeDataSchema,
1313
entityNodeDataSchema,
1414
graphRequestSchema,
1515
graphResponseSchema,
1616
groupNodeDataSchema,
1717
labelNodeDataSchema,
18+
nodeColorSchema,
1819
nodeShapeSchema,
1920
} from '../../schema/graph/v1';
2021

@@ -25,7 +26,8 @@ export type GraphResponse = Omit<TypeOf<typeof graphResponseSchema>, 'messages'>
2526
messages?: ApiMessageCode[];
2627
};
2728

28-
export type Color = typeof colorSchema.type;
29+
export type EdgeColor = typeof edgeColorSchema.type;
30+
export type NodeColor = typeof nodeColorSchema.type;
2931

3032
export type NodeShape = TypeOf<typeof nodeShapeSchema>;
3133

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7489,14 +7489,6 @@
74897489
"securitySolutionPackages.csp.graph.controls.toggleSearchBar.tour.title": "Affinez votre vue avec la recherche",
74907490
"securitySolutionPackages.csp.graph.controls.zoomIn": "Zoom avant",
74917491
"securitySolutionPackages.csp.graph.controls.zoomOut": "Zoom arrière",
7492-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.hideEventsWithThisAction": "Masquer les événements avec cette action",
7493-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.showEventsWithThisAction": "Afficher les événements avec cette action",
7494-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsByEntity": "Masquer les actions par cette entité",
7495-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsOnEntity": "Masquer les actions sur cette entité",
7496-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideRelatedEntities": "Masquer les entités liées",
7497-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsByEntity": "Afficher les actions par cette entité",
7498-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsOnEntity": "Afficher les actions sur cette entité",
7499-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showRelatedEntities": "Afficher les entités liées",
75007492
"securitySolutionPackages.csp.graph.investigation.errorBuildingQuery": "Impossible d'extraire les résultats de recherche",
75017493
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterContent": "Un ou plusieurs filtres sont annulés et ne renverront donc peut-être pas les résultats attendus.",
75027494
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterTitle": "Filtres annulés",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7489,14 +7489,6 @@
74897489
"securitySolutionPackages.csp.graph.controls.toggleSearchBar.tour.title": "検索でビューを絞り込む",
74907490
"securitySolutionPackages.csp.graph.controls.zoomIn": "ズームイン",
74917491
"securitySolutionPackages.csp.graph.controls.zoomOut": "ズームアウト",
7492-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.hideEventsWithThisAction": "このアクションのイベントを非表示",
7493-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.showEventsWithThisAction": "このアクションのイベントを表示",
7494-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsByEntity": "このエンティティによるアクションを非表示",
7495-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsOnEntity": "このエンティティに対するアクションを非表示",
7496-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideRelatedEntities": "関連するエンティティを非表示",
7497-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsByEntity": "このエンティティによるアクションを表示",
7498-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsOnEntity": "このエンティティに対するアクションを表示",
7499-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showRelatedEntities": "関連するエンティティを表示",
75007492
"securitySolutionPackages.csp.graph.investigation.errorBuildingQuery": "検索結果を取得できません",
75017493
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterContent": "1つ以上のフィルターが否定され、意図した結果が返されない場合があります。",
75027494
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterTitle": "フィルターが否定されました",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7484,14 +7484,6 @@
74847484
"securitySolutionPackages.csp.graph.controls.toggleSearchBar.tour.title": "通过搜索优化您的视图",
74857485
"securitySolutionPackages.csp.graph.controls.zoomIn": "放大",
74867486
"securitySolutionPackages.csp.graph.controls.zoomOut": "缩小",
7487-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.hideEventsWithThisAction": "通过此操作隐藏事件",
7488-
"securitySolutionPackages.csp.graph.graphLabelExpandPopover.showEventsWithThisAction": "通过此操作显示事件",
7489-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsByEntity": "隐藏此实体执行的操作",
7490-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideActionsOnEntity": "隐藏对此实体执行的操作",
7491-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.hideRelatedEntities": "隐藏相关实体",
7492-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsByEntity": "显示此实体执行的操作",
7493-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showActionsOnEntity": "显示对此实体执行的操作",
7494-
"securitySolutionPackages.csp.graph.graphNodeExpandPopover.showRelatedEntities": "显示相关实体",
74957487
"securitySolutionPackages.csp.graph.investigation.errorBuildingQuery": "无法检索搜索结果",
74967488
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterContent": "一个或多个筛选已作废,可能无法返回预期结果。",
74977489
"securitySolutionPackages.csp.graph.investigation.warningNegatedFilterTitle": "筛选已作废",
Lines changed: 5 additions & 0 deletions
Loading

x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/common/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export const SHOW_SEARCH_BAR_BUTTON_TOUR_STORAGE_KEY =
1818
export const TOGGLE_SEARCH_BAR_STORAGE_KEY =
1919
'securitySolution.graphInvestigation:toggleSearchBarState' as const;
2020

21-
export const GRAPH_NODES_LIMIT = 100;
21+
export const GRAPH_NODES_LIMIT = 300;

x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/actions.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export const Actions = ({
133133
: undefined;
134134

135135
return (
136-
<EuiFlexGroup direction="column" gutterSize={'none'} {...props}>
136+
<EuiFlexGroup direction="column" gutterSize="none" {...props}>
137137
{showToggleSearch && (
138138
<EuiFlexItem grow={false}>
139139
<EuiTourStep
@@ -154,6 +154,7 @@ export const Actions = ({
154154
css={[
155155
css`
156156
position: relative;
157+
overflow: visible;
157158
width: 40px;
158159
`,
159160
!searchToggled
@@ -163,6 +164,11 @@ export const Actions = ({
163164
`
164165
: undefined,
165166
]}
167+
contentProps={{
168+
css: css`
169+
position: initial;
170+
`,
171+
}}
166172
minWidth={false}
167173
size="m"
168174
aria-label={toggleSearchBarTooltip}
@@ -209,7 +215,11 @@ export const Actions = ({
209215
</EuiTourStep>
210216
</EuiFlexItem>
211217
)}
212-
{showToggleSearch && showInvestigateInTimeline && <EuiHorizontalRule margin="xs" />}
218+
{showToggleSearch && showInvestigateInTimeline && (
219+
<EuiFlexItem grow={false}>
220+
<EuiHorizontalRule margin="xs" />
221+
</EuiFlexItem>
222+
)}
213223
{showInvestigateInTimeline && (
214224
<EuiFlexItem grow={false}>
215225
<EuiToolTip content={investigateInTimelineTooltip} position="left">

x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/controls/controls.tsx

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
EuiButtonIcon,
1111
EuiFlexGroup,
1212
EuiFlexItem,
13-
EuiSpacer,
13+
EuiHorizontalRule,
14+
EuiIcon,
1415
useEuiTheme,
1516
type CommonProps,
1617
} from '@elastic/eui';
@@ -23,6 +24,7 @@ import {
2324
GRAPH_CONTROLS_ZOOM_IN_ID,
2425
GRAPH_CONTROLS_ZOOM_OUT_ID,
2526
} from '../test_ids';
27+
import fitToViewIcon from '../../assets/icons/fit_to_view.svg';
2628

2729
const selector = (s: ReactFlowState) => ({
2830
minZoomReached: s.transform[2] <= s.minZoom,
@@ -57,6 +59,8 @@ const CenterLabel = i18n.translate('securitySolutionPackages.csp.graph.controls.
5759
defaultMessage: 'Center',
5860
});
5961

62+
const fitToViewIconFn = () => <EuiIcon type={fitToViewIcon} size="m" color="text" />;
63+
6064
export const Controls = ({
6165
showZoom = true,
6266
showFitView = true,
@@ -73,12 +77,12 @@ export const Controls = ({
7377
const { maxZoomReached, minZoomReached } = useStore(selector);
7478

7579
const onZoomInHandler = () => {
76-
zoomIn();
80+
zoomIn({ duration: fitViewOptions?.duration });
7781
onZoomIn?.();
7882
};
7983

8084
const onZoomOutHandler = () => {
81-
zoomOut();
85+
zoomOut({ duration: fitViewOptions?.duration });
8286
onZoomOut?.();
8387
};
8488

@@ -88,57 +92,68 @@ export const Controls = ({
8892
};
8993

9094
const btnCss = css`
95+
border-radius: 0;
96+
`;
97+
98+
const groupCss = css`
9199
border: ${euiTheme.border.thin};
92100
border-radius: ${euiTheme.border.radius.medium};
93101
background-color: ${euiTheme.colors.backgroundBasePlain};
94-
box-sizing: content-box;
95102
`;
96103

97104
if (!showZoom && !showCenter && !showFitView) {
98105
return <></>;
99106
}
100107

101108
return (
102-
<EuiFlexGroup direction="column" gutterSize={'none'} {...props}>
109+
<EuiFlexGroup direction="column" gutterSize="none" css={groupCss} {...props}>
103110
{showZoom && (
104-
<EuiFlexItem grow={false} css={btnCss}>
105-
<EuiButtonIcon
106-
iconType="plusInCircle"
107-
aria-label={ZoomInLabel}
108-
size="m"
109-
color="text"
110-
data-test-subj={GRAPH_CONTROLS_ZOOM_IN_ID}
111-
disabled={maxZoomReached}
112-
onClick={onZoomInHandler}
113-
/>
111+
<>
112+
<EuiFlexItem grow={false}>
113+
<EuiButtonIcon
114+
iconType="plusInCircle"
115+
aria-label={ZoomInLabel}
116+
size="m"
117+
color="text"
118+
data-test-subj={GRAPH_CONTROLS_ZOOM_IN_ID}
119+
disabled={maxZoomReached}
120+
css={btnCss}
121+
onClick={onZoomInHandler}
122+
/>
123+
</EuiFlexItem>
124+
<EuiFlexItem grow={false}>
125+
<EuiButtonIcon
126+
iconType="minusInCircle"
127+
aria-label={ZoomOutLabel}
128+
size="m"
129+
color="text"
130+
data-test-subj={GRAPH_CONTROLS_ZOOM_OUT_ID}
131+
disabled={minZoomReached}
132+
css={btnCss}
133+
onClick={onZoomOutHandler}
134+
/>
135+
</EuiFlexItem>
136+
</>
137+
)}
138+
{showCenter && (
139+
<EuiFlexItem grow={false}>
140+
{showZoom ? <EuiHorizontalRule size="full" margin="none" /> : null}
114141
<EuiButtonIcon
115-
iconType="minusInCircle"
116-
aria-label={ZoomOutLabel}
142+
iconType="bullseye"
143+
aria-label={CenterLabel}
117144
size="m"
118145
color="text"
119-
data-test-subj={GRAPH_CONTROLS_ZOOM_OUT_ID}
120-
disabled={minZoomReached}
121-
onClick={onZoomOutHandler}
146+
data-test-subj={GRAPH_CONTROLS_CENTER_ID}
147+
css={btnCss}
148+
onClick={() => onCenter?.()}
122149
/>
123150
</EuiFlexItem>
124151
)}
125-
{showZoom && showCenter && <EuiSpacer size="xs" />}
126-
{showCenter && (
127-
<EuiButtonIcon
128-
iconType="bullseye"
129-
aria-label={CenterLabel}
130-
size="m"
131-
color="text"
132-
data-test-subj={GRAPH_CONTROLS_CENTER_ID}
133-
css={btnCss}
134-
onClick={() => onCenter?.()}
135-
/>
136-
)}
137-
{(showZoom || showCenter) && showFitView && <EuiSpacer size="xs" />}
138152
{showFitView && (
139153
<EuiFlexItem grow={false}>
154+
{showZoom || showCenter ? <EuiHorizontalRule size="full" margin="none" /> : null}
140155
<EuiButtonIcon
141-
iconType="continuityWithin"
156+
iconType={fitToViewIconFn}
142157
aria-label={FitViewLabel}
143158
size="m"
144159
color="text"

x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/edge/deafult_edge.stories.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const edgeTypes = {
6464
const Template = (args: EdgeViewModel) => {
6565
const isArrayOfObjectsEqual = (x: object[], y: object[]) =>
6666
size(x) === size(y) && isEmpty(xorWith(x, y, isEqual));
67+
const edgeData = pick(args, ['id', 'label', 'interactive', 'source', 'target', 'color', 'type']);
6768

6869
const nodes = useMemo(
6970
() => [
@@ -86,11 +87,14 @@ const Template = (args: EdgeViewModel) => {
8687
{
8788
id: args.id,
8889
type: 'label',
89-
data: pick(args, ['id', 'label', 'interactive', 'source', 'target', 'color', 'type']),
90+
data: {
91+
...edgeData,
92+
color: edgeData.color === 'subdued' ? 'primary' : edgeData.color,
93+
},
9094
position: { x: 230, y: 6 },
9195
},
9296
],
93-
[args]
97+
[args, edgeData]
9498
);
9599

96100
const edges = useMemo(
@@ -172,7 +176,7 @@ export default {
172176
render: Template,
173177
argTypes: {
174178
color: {
175-
options: ['primary', 'danger', 'warning'],
179+
options: ['primary', 'danger', 'warning', 'subdued'],
176180
control: { type: 'radio' },
177181
},
178182
type: {

0 commit comments

Comments
 (0)