Skip to content

Commit

Permalink
Added filterbar support
Browse files Browse the repository at this point in the history
  • Loading branch information
kfirpeled committed Oct 30, 2024
1 parent 6f71b19 commit 3dc4523
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useState, useCallback, useEffect } from 'react';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { size, isEmpty, isEqual, xorWith } from 'lodash';
import {
Background,
Expand All @@ -15,7 +15,7 @@ import {
useEdgesState,
useNodesState,
} from '@xyflow/react';
import type { Edge, Node } from '@xyflow/react';
import type { Edge, FitViewOptions, Node } from '@xyflow/react';
import type { CommonProps } from '@elastic/eui';
import { SvgDefsMarker } from '../edge/styles';
import {
Expand Down Expand Up @@ -89,6 +89,9 @@ export const Graph: React.FC<GraphProps> = ({
...rest
}) => {
const backgroundId = Math.random().toFixed(4); // TODO: use useId(); when available (react >=18)
const fitViewRef = useRef<
((fitViewOptions?: FitViewOptions<Node> | undefined) => Promise<boolean>) | null
>(null);
const [prevNodes, setPrevNodes] = useState<NodeViewModel[]>([]);
const [prevEdges, setPrevEdges] = useState<EdgeViewModel[]>([]);
const [isGraphLocked, setIsGraphLocked] = useState(!interactive);
Expand All @@ -105,6 +108,9 @@ export const Graph: React.FC<GraphProps> = ({
setEdges(initialEdges);
setPrevNodes(nodes);
setPrevEdges(edges);
setTimeout(() => {
fitViewRef.current?.();
}, 0);
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this effect when nodes or edges change
}, [nodes, edges, setNodes, setEdges]);
Expand Down Expand Up @@ -132,6 +138,7 @@ export const Graph: React.FC<GraphProps> = ({
fitView={true}
onInit={(xyflow) => {
window.requestAnimationFrame(() => xyflow.fitView());
fitViewRef.current = xyflow.fitView;

// When the graph is not initialized as interactive, we need to fit the view on resize
if (!interactive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export const GraphPopover: React.FC<GraphPopoverProps> = ({
disabled: false,
crossFrame: true,
noIsolation: false,
returnFocus: false,
returnFocus: (el) => {
anchorElement.focus();
return false;
},
preventScrollOnFocus: true,
onClickOutside: () => {
closePopover();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import React, { memo, useMemo, useState } from 'react';
import { SearchBar } from '@kbn/unified-search-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { NodeViewModel } from '@kbn/cloud-security-posture-graph';
import type { Query } from '@kbn/es-query';
import type { Filter, Query } from '@kbn/es-query';
import { css } from '@emotion/css';
import { useSourcererDataView } from '../../../../sourcerer/containers';
import { SourcererScopeName } from '../../../../sourcerer/store/model';
import { useDocumentDetailsContext } from '../../shared/context';
import { GRAPH_VISUALIZATION_TEST_ID } from './test_ids';
import { useFetchGraphData } from '../../right/hooks/use_fetch_graph_data';
Expand Down Expand Up @@ -61,7 +63,10 @@ const useGraphData = (
return { data, refresh, isFetching };
};

const useGraphPopovers = (setActorIds: React.Dispatch<React.SetStateAction<Set<string>>>) => {
const useGraphPopovers = (
setActorIds: React.Dispatch<React.SetStateAction<Set<string>>>,
setSearchFilters: React.Dispatch<React.SetStateAction<Filter[]>>
) => {
const {
services: { notifications },
} = useKibana();
Expand All @@ -72,6 +77,29 @@ const useGraphPopovers = (setActorIds: React.Dispatch<React.SetStateAction<Set<s
},
onShowActionsByEntityClick: (node) => {
setActorIds((prev) => new Set([...prev, node.id]));
setSearchFilters((prev) => [
...prev,
{
meta: {
disabled: false,
key: 'actor.entity.id',
field: 'actor.entity.id',
negate: false,
params: {
query: node.id,
},
type: 'phrase',
},
query: {
match_phrase: {
'actor.entity.id': node.id,
},
},
$state: {
store: 'appState',
} as Filter['$state'],
},
]);
},
onShowActionsOnEntityClick: (node) => {
notifications?.toasts.addInfo('Show actions on entity is not implemented yet');
Expand Down Expand Up @@ -114,6 +142,8 @@ const useGraphNodes = (
* Graph visualization view displayed in the document details expandable flyout left section under the Visualize tab
*/
export const GraphVisualization: React.FC = memo(() => {
const { indexPattern } = useSourcererDataView(SourcererScopeName.default);
const [searchFilters, setSearchFilters] = useState<Filter[]>(() => []);
const { filters, updateFilters } = useGraphFilters();
const { getFieldsData, dataAsNestedObject } = useDocumentDetailsContext();
const { eventIds } = useGraphPreview({
Expand All @@ -122,7 +152,7 @@ export const GraphVisualization: React.FC = memo(() => {
});

const [actorIds, setActorIds] = useState(() => new Set<string>());
const { nodeExpandPopover, popoverOpenWrapper } = useGraphPopovers(setActorIds);
const { nodeExpandPopover, popoverOpenWrapper } = useGraphPopovers(setActorIds, setSearchFilters);
const expandButtonClickHandler = (...args: unknown[]) =>
popoverOpenWrapper(nodeExpandPopover.onNodeExpandButtonClick, ...args);
const isPopoverOpen = [nodeExpandPopover].some(({ state: { isOpen } }) => isOpen);
Expand All @@ -135,7 +165,7 @@ export const GraphVisualization: React.FC = memo(() => {
{...{
appName: 'graph-visualization',
intl: null,
showFilterBar: false,
showFilterBar: true,
showDatePicker: true,
showAutoRefreshOnly: false,
showSaveQuery: false,
Expand All @@ -145,12 +175,24 @@ export const GraphVisualization: React.FC = memo(() => {
dateRangeFrom: filters.from.split('/')[0],
dateRangeTo: filters.to.split('/')[0],
query: { query: '', language: 'kuery' },
filters: [],
indexPatterns: [indexPattern],
filters: searchFilters,
submitButtonStyle: 'iconOnly',
onFiltersUpdated: (newFilters) => {
setSearchFilters(newFilters);

setActorIds(
new Set(
newFilters
.filter((filter) => filter.meta.key === 'actor.entity.id')
.map((filter) => (filter.meta.params as { query: string })?.query)
.filter((query) => typeof query === 'string')
)
);
},
onQuerySubmit: (payload, isUpdate) => {
if (isUpdate) {
updateFilters({ from: payload.dateRange.from, to: payload.dateRange.to });
setActorIds(new Set<string>());
} else {
refresh();
}
Expand Down

0 comments on commit 3dc4523

Please sign in to comment.