Skip to content

Commit

Permalink
[8.x] [Cloud Security] Added labels popover (elastic#204954) (elastic…
Browse files Browse the repository at this point in the history
…#205111)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Cloud Security] Added labels popover
(elastic#204954)](elastic#204954)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Kfir
Peled","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-23T16:47:24Z","message":"[Cloud
Security] Added labels popover (elastic#204954)\n\n## Summary\r\n\r\nAdded
expand button for labels with
popover:\r\n\r\n\r\nhttps://github.com/user-attachments/assets/80950f51-b45b-4174-9be2-267b6aca569b\r\n\r\n\r\nhttps://github.com/user-attachments/assets/690ef85b-be48-42df-bf00-02ee7d9303f2\r\n\r\n**How
to test**\r\n\r\nTo test this PR you can run\r\n\r\n```\r\nyarn
storybook cloud_security_posture_packages\r\n```\r\n\r\nTo test
e2e\r\n\r\n- Enable the feature
flag\r\n\r\n`kibana.dev.yml`:\r\n\r\n```yml\r\nuiSettings.overrides.securitySolution:enableVisualizationsInFlyout:
true\r\nxpack.securitySolution.enableExperimental:
['graphVisualizationInFlyoutEnabled']\r\n```\r\n\r\n- Load mocked
data:\r\n\r\n```\r\nnode scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/logs_gcp_audit
\\ \r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n\r\nnode
scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/security_alerts
\\\r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n```\r\n\r\n- Make
sure you include data from Oct 13 2024. (in the video I use
Last\r\nyear)\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows
[EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by:
Sean Rathier <[email protected]>\r\nCo-authored-by: Brad White
<[email protected]>\r\nCo-authored-by: seanrathier
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"fd47d2ec7ccd9188fa45af85d44a17d143f2e44a","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Cloud
Security","backport:prev-minor","ci:build-storybooks"],"title":"[Cloud
Security] Added labels
popover","number":204954,"url":"https://github.com/elastic/kibana/pull/204954","mergeCommit":{"message":"[Cloud
Security] Added labels popover (elastic#204954)\n\n## Summary\r\n\r\nAdded
expand button for labels with
popover:\r\n\r\n\r\nhttps://github.com/user-attachments/assets/80950f51-b45b-4174-9be2-267b6aca569b\r\n\r\n\r\nhttps://github.com/user-attachments/assets/690ef85b-be48-42df-bf00-02ee7d9303f2\r\n\r\n**How
to test**\r\n\r\nTo test this PR you can run\r\n\r\n```\r\nyarn
storybook cloud_security_posture_packages\r\n```\r\n\r\nTo test
e2e\r\n\r\n- Enable the feature
flag\r\n\r\n`kibana.dev.yml`:\r\n\r\n```yml\r\nuiSettings.overrides.securitySolution:enableVisualizationsInFlyout:
true\r\nxpack.securitySolution.enableExperimental:
['graphVisualizationInFlyoutEnabled']\r\n```\r\n\r\n- Load mocked
data:\r\n\r\n```\r\nnode scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/logs_gcp_audit
\\ \r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n\r\nnode
scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/security_alerts
\\\r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n```\r\n\r\n- Make
sure you include data from Oct 13 2024. (in the video I use
Last\r\nyear)\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows
[EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by:
Sean Rathier <[email protected]>\r\nCo-authored-by: Brad White
<[email protected]>\r\nCo-authored-by: seanrathier
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"fd47d2ec7ccd9188fa45af85d44a17d143f2e44a"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/204954","number":204954,"mergeCommit":{"message":"[Cloud
Security] Added labels popover (elastic#204954)\n\n## Summary\r\n\r\nAdded
expand button for labels with
popover:\r\n\r\n\r\nhttps://github.com/user-attachments/assets/80950f51-b45b-4174-9be2-267b6aca569b\r\n\r\n\r\nhttps://github.com/user-attachments/assets/690ef85b-be48-42df-bf00-02ee7d9303f2\r\n\r\n**How
to test**\r\n\r\nTo test this PR you can run\r\n\r\n```\r\nyarn
storybook cloud_security_posture_packages\r\n```\r\n\r\nTo test
e2e\r\n\r\n- Enable the feature
flag\r\n\r\n`kibana.dev.yml`:\r\n\r\n```yml\r\nuiSettings.overrides.securitySolution:enableVisualizationsInFlyout:
true\r\nxpack.securitySolution.enableExperimental:
['graphVisualizationInFlyoutEnabled']\r\n```\r\n\r\n- Load mocked
data:\r\n\r\n```\r\nnode scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/logs_gcp_audit
\\ \r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n\r\nnode
scripts/es_archiver load
x-pack/test/cloud_security_posture_functional/es_archives/security_alerts
\\\r\n --es-url http://elastic:changeme@localhost:9200 \\\r\n
--kibana-url http://elastic:changeme@localhost:5601\r\n```\r\n\r\n- Make
sure you include data from Oct 13 2024. (in the video I use
Last\r\nyear)\r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows
[EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by:
Sean Rathier <[email protected]>\r\nCo-authored-by: Brad White
<[email protected]>\r\nCo-authored-by: seanrathier
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"fd47d2ec7ccd9188fa45af85d44a17d143f2e44a"}}]}]
BACKPORT-->

Co-authored-by: Kfir Peled <[email protected]>
  • Loading branch information
kibanamachine and kfirpeled authored Dec 23, 2024
1 parent 1febffd commit 3424052
Show file tree
Hide file tree
Showing 31 changed files with 376 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export const EVENT_GRAPH_VISUALIZATION_API = '/internal/cloud_security_posture/g
export const RELATED_ENTITY = 'related.entity' as const;
export const ACTOR_ENTITY_ID = 'actor.entity.id' as const;
export const TARGET_ENTITY_ID = 'target.entity.id' as const;
export const EVENT_ACTION = 'event.action' as const;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useEffect, useMemo, useRef } from 'react';
import React, { memo, useEffect, useMemo, useRef } from 'react';
import { ThemeProvider } from '@emotion/react';
import {
ReactFlow,
Expand Down Expand Up @@ -47,7 +47,7 @@ export default {

const nodeTypes = {
// eslint-disable-next-line react/display-name
default: React.memo((props: NodeProps<BuiltInNode>) => {
default: memo<NodeProps<BuiltInNode>>((props: NodeProps<BuiltInNode>) => {
return (
<div>
<Handle
Expand All @@ -67,7 +67,7 @@ const nodeTypes = {
{props.data.label}
</div>
);
}) as React.FC<NodeProps<BuiltInNode>>,
}),
label: LabelNode,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,7 @@ const edgeTypes = {
*
* @returns {JSX.Element} The rendered Graph component.
*/
export const Graph: React.FC<GraphProps> = ({
nodes,
edges,
interactive,
isLocked = false,
...rest
}) => {
export const Graph = ({ nodes, edges, interactive, isLocked = false, ...rest }: GraphProps) => {
const backgroundId = useGeneratedHtmlId();
const fitViewRef = useRef<
((fitViewOptions?: FitViewOptions<Node> | undefined) => Promise<boolean>) | null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from '
import { ThemeProvider, css } from '@emotion/react';
import { Story } from '@storybook/react';
import { EuiListGroup, EuiHorizontalRule } from '@elastic/eui';
import type { EntityNodeViewModel, NodeProps } from '..';
import type { EntityNodeViewModel, LabelNodeViewModel, NodeProps } from '..';
import { Graph } from '..';
import { GraphPopover } from './graph_popover';
import { ExpandButtonClickCallback } from '../types';
Expand Down Expand Up @@ -179,9 +179,11 @@ const Template: Story = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nodeClickHandler = (...args: any[]) => popoverOpenWrapper(nodePopover.onNodeClick, ...args);

const nodes: EntityNodeViewModel[] = useMemo(
() =>
(['hexagon', 'ellipse', 'rectangle', 'pentagon', 'diamond'] as const).map((shape, idx) => ({
const nodes: Array<EntityNodeViewModel | LabelNodeViewModel> = useMemo(
() => [
...(
['hexagon', 'ellipse', 'rectangle', 'pentagon', 'diamond'] as const
).map<EntityNodeViewModel>((shape, idx) => ({
id: `${idx}`,
label: `Node ${idx}`,
color: 'primary',
Expand All @@ -191,6 +193,16 @@ const Template: Story = () => {
expandButtonClick: expandButtonClickHandler,
nodeClick: nodeClickHandler,
})),
{
id: 'label',
label: 'Node 5',
color: 'primary',
interactive: true,
shape: 'label',
expandButtonClick: expandButtonClickHandler,
nodeClick: nodeClickHandler,
} as LabelNodeViewModel,
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ export interface GraphPopoverProps
closePopover: () => void;
}

export const GraphPopover: React.FC<GraphPopoverProps> = ({
export const GraphPopover = ({
isOpen,
anchorElement,
closePopover,
children,
...rest
}) => {
}: GraphPopoverProps) => {
const { euiTheme } = useEuiTheme();

if (!anchorElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ import type { Filter, Query, TimeRange, PhraseFilter } from '@kbn/es-query';
import { css } from '@emotion/react';
import { getEsQueryConfig } from '@kbn/data-service';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Graph } from '../../..';
import { Graph, isEntityNode } from '../../..';
import { useGraphNodeExpandPopover } from './use_graph_node_expand_popover';
import { useGraphLabelExpandPopover } from './use_graph_label_expand_popover';
import { useFetchGraphData } from '../../hooks/use_fetch_graph_data';
import { GRAPH_INVESTIGATION_TEST_ID } from '../test_ids';
import { ACTOR_ENTITY_ID, RELATED_ENTITY, TARGET_ENTITY_ID } from '../../common/constants';
import {
ACTOR_ENTITY_ID,
EVENT_ACTION,
RELATED_ENTITY,
TARGET_ENTITY_ID,
} from '../../common/constants';

const CONTROLLED_BY_GRAPH_INVESTIGATION_FILTER = 'graph-investigation';

Expand Down Expand Up @@ -112,17 +118,23 @@ const useGraphPopovers = (
},
});

const labelExpandPopover = useGraphLabelExpandPopover({
onShowEventsWithThisActionClick: (node) => {
setSearchFilters((prev) => addFilter(dataViewId, prev, EVENT_ACTION, node.data.label ?? ''));
},
});

const openPopoverCallback = useCallback(
(cb: Function, ...args: unknown[]) => {
[nodeExpandPopover].forEach(({ actions: { closePopover } }) => {
[nodeExpandPopover, labelExpandPopover].forEach(({ actions: { closePopover } }) => {
closePopover();
});
cb(...args);
},
[nodeExpandPopover]
[nodeExpandPopover, labelExpandPopover]
);

return { nodeExpandPopover, openPopoverCallback };
return { nodeExpandPopover, labelExpandPopover, openPopoverCallback };
};

interface GraphInvestigationProps {
Expand Down Expand Up @@ -160,7 +172,7 @@ interface GraphInvestigationProps {
/**
* Graph investigation view allows the user to expand nodes and view related entities.
*/
export const GraphInvestigation: React.FC<GraphInvestigationProps> = memo(
export const GraphInvestigation = memo<GraphInvestigationProps>(
({
initialState: { dataView, originEventIds, timeRange: initialTimeRange },
}: GraphInvestigationProps) => {
Expand All @@ -181,13 +193,17 @@ export const GraphInvestigation: React.FC<GraphInvestigationProps> = memo(
[dataView, searchFilters, uiSettings]
);

const { nodeExpandPopover, openPopoverCallback } = useGraphPopovers(
const { nodeExpandPopover, labelExpandPopover, openPopoverCallback } = useGraphPopovers(
dataView?.id ?? '',
setSearchFilters
);
const expandButtonClickHandler = (...args: unknown[]) =>
const nodeExpandButtonClickHandler = (...args: unknown[]) =>
openPopoverCallback(nodeExpandPopover.onNodeExpandButtonClick, ...args);
const isPopoverOpen = [nodeExpandPopover].some(({ state: { isOpen } }) => isOpen);
const labelExpandButtonClickHandler = (...args: unknown[]) =>
openPopoverCallback(labelExpandPopover.onLabelExpandButtonClick, ...args);
const isPopoverOpen = [nodeExpandPopover, labelExpandPopover].some(
({ state: { isOpen } }) => isOpen
);
const { data, refresh, isFetching } = useFetchGraphData({
req: {
query: {
Expand All @@ -206,13 +222,19 @@ export const GraphInvestigation: React.FC<GraphInvestigationProps> = memo(
const nodes = useMemo(() => {
return (
data?.nodes.map((node) => {
const nodeHandlers =
node.shape !== 'label' && node.shape !== 'group'
? {
expandButtonClick: expandButtonClickHandler,
}
: undefined;
return { ...node, ...nodeHandlers };
if (isEntityNode(node)) {
return {
...node,
expandButtonClick: nodeExpandButtonClickHandler,
};
} else if (node.shape === 'label') {
return {
...node,
expandButtonClick: labelExpandButtonClickHandler,
};
}

return { ...node };
}) ?? []
);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -275,6 +297,7 @@ export const GraphInvestigation: React.FC<GraphInvestigationProps> = memo(
</EuiFlexItem>
</EuiFlexGroup>
<nodeExpandPopover.PopoverComponent />
<labelExpandPopover.PopoverComponent />
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { memo } from 'react';
import { EuiListGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ExpandPopoverListItem } from '../styles';
import { GraphPopover } from '../../..';
import {
GRAPH_LABEL_EXPAND_POPOVER_TEST_ID,
GRAPH_LABEL_EXPAND_POPOVER_SHOW_EVENTS_WITH_THIS_ACTION_ITEM_ID,
} from '../test_ids';

interface GraphLabelExpandPopoverProps {
isOpen: boolean;
anchorElement: HTMLElement | null;
closePopover: () => void;
onShowEventsWithThisActionClick: () => void;
}

export const GraphLabelExpandPopover = memo<GraphLabelExpandPopoverProps>(
({ isOpen, anchorElement, closePopover, onShowEventsWithThisActionClick }) => {
return (
<GraphPopover
panelPaddingSize="s"
anchorPosition="rightCenter"
isOpen={isOpen}
anchorElement={anchorElement}
closePopover={closePopover}
data-test-subj={GRAPH_LABEL_EXPAND_POPOVER_TEST_ID}
>
<EuiListGroup gutterSize="none" bordered={false} flush={true}>
<ExpandPopoverListItem
iconType="users"
label={i18n.translate(
'securitySolutionPackages.csp.graph.graphLabelExpandPopover.showEventsWithThisAction',
{ defaultMessage: 'Show events with this action' }
)}
onClick={onShowEventsWithThisActionClick}
data-test-subj={GRAPH_LABEL_EXPAND_POPOVER_SHOW_EVENTS_WITH_THIS_ACTION_ITEM_ID}
/>
</EuiListGroup>
</GraphPopover>
);
}
);

GraphLabelExpandPopover.displayName = 'GraphLabelExpandPopover';
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface GraphNodeExpandPopoverProps {
onShowActionsOnEntityClick: () => void;
}

export const GraphNodeExpandPopover: React.FC<GraphNodeExpandPopoverProps> = memo(
export const GraphNodeExpandPopover = memo<GraphNodeExpandPopoverProps>(
({
isOpen,
anchorElement,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useGraphPopover } from '../../..';
import type { ExpandButtonClickCallback, NodeProps } from '../types';
import type { PopoverActions } from '../graph/use_graph_popover';
import { GraphLabelExpandPopover } from './graph_label_expand_popover';

interface UseGraphLabelExpandPopoverArgs {
onShowEventsWithThisActionClick: (node: NodeProps) => void;
}

export const useGraphLabelExpandPopover = ({
onShowEventsWithThisActionClick,
}: UseGraphLabelExpandPopoverArgs) => {
const { id, state, actions } = useGraphPopover('label-expand-popover');
const { openPopover, closePopover } = actions;

const selectedNode = useRef<NodeProps | null>(null);
const unToggleCallbackRef = useRef<(() => void) | null>(null);
const [pendingOpen, setPendingOpen] = useState<{
node: NodeProps;
el: HTMLElement;
unToggleCallback: () => void;
} | null>(null);

const closePopoverHandler = useCallback(() => {
selectedNode.current = null;
unToggleCallbackRef.current?.();
unToggleCallbackRef.current = null;
closePopover();
}, [closePopover]);

const onLabelExpandButtonClick: ExpandButtonClickCallback = useCallback(
(e, node, unToggleCallback) => {
if (selectedNode.current?.id === node.id) {
// If the same node is clicked again, close the popover
closePopoverHandler();
} else {
// Close the current popover if open
closePopoverHandler();

// Set the pending open state
setPendingOpen({ node, el: e.currentTarget, unToggleCallback });
}
},
[closePopoverHandler]
);

useEffect(() => {
// Open pending popover if the popover is not open
if (!state.isOpen && pendingOpen) {
const { node, el, unToggleCallback } = pendingOpen;

selectedNode.current = node;
unToggleCallbackRef.current = unToggleCallback;
openPopover(el);

setPendingOpen(null);
}
}, [state.isOpen, pendingOpen, openPopover]);

const PopoverComponent = memo(() => (
<GraphLabelExpandPopover
isOpen={state.isOpen}
anchorElement={state.anchorElement}
closePopover={closePopoverHandler}
onShowEventsWithThisActionClick={() => {
onShowEventsWithThisActionClick(selectedNode.current as NodeProps);
closePopoverHandler();
}}
/>
));

PopoverComponent.displayName = GraphLabelExpandPopover.displayName;

const actionsWithClose: PopoverActions = useMemo(
() => ({
...actions,
closePopover: closePopoverHandler,
}),
[actions, closePopoverHandler]
);

return useMemo(
() => ({
onLabelExpandButtonClick,
PopoverComponent,
id,
actions: actionsWithClose,
state,
}),
[PopoverComponent, actionsWithClose, id, onLabelExpandButtonClick, state]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { NodeViewModel } from './types';

export { Graph } from './graph/graph';
export { GraphInvestigation } from './graph_investigation/graph_investigation';
export { GraphPopover } from './graph/graph_popover';
Expand All @@ -18,3 +20,10 @@ export type {
EntityNodeViewModel,
NodeProps,
} from './types';

export const isEntityNode = (node: NodeViewModel) =>
node.shape === 'ellipse' ||
node.shape === 'pentagon' ||
node.shape === 'rectangle' ||
node.shape === 'diamond' ||
node.shape === 'hexagon';
Loading

0 comments on commit 3424052

Please sign in to comment.