From 9a8d0266b647e730d494ac712041517dad7502f5 Mon Sep 17 00:00:00 2001 From: christineweng Date: Thu, 9 May 2024 14:49:43 -0500 Subject: [PATCH] analyzer in flyout poc --- .../common/experimental_features.ts | 5 ++ .../left/components/analyze_graph.tsx | 5 +- .../flyout/document_details/left/index.tsx | 19 ++++-- .../preview/components/analyzer_panel.tsx | 65 +++++++++++++++++++ .../flyout/document_details/preview/index.tsx | 3 +- .../document_details/preview/panels.tsx | 5 ++ .../public/resolver/view/controls/index.tsx | 15 ++++- .../public/resolver/view/controls/panel.tsx | 51 +++++++++++++++ .../resolver/view/panels/event_detail.tsx | 17 +++-- .../resolver/view/panels/node_detail.tsx | 14 ++-- .../resolver/view/panels/node_events.tsx | 11 ++-- .../view/panels/node_events_of_type.tsx | 14 ++-- .../public/resolver/view/panels/node_list.tsx | 9 ++- .../view/resolver_without_providers.tsx | 6 +- 14 files changed, 200 insertions(+), 39 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/preview/components/analyzer_panel.tsx create mode 100644 x-pack/plugins/security_solution/public/resolver/view/controls/panel.tsx diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index f21f9e82ad92f3..afe87f27072437 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -241,6 +241,11 @@ export const allowedExperimentalValues = Object.freeze({ */ analyzerDatePickersAndSourcererDisabled: false, + /** + * Enables visualization: session viewer and analyzer in expandable flyout + */ + visualizationInFlyoutEnabled: false, + /** * Enables per-field rule diffs tab in the prebuilt rule upgrade flyout * diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/analyze_graph.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/analyze_graph.tsx index 6a252296983a34..0c05d136b4b6dd 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/analyze_graph.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/analyze_graph.tsx @@ -20,8 +20,7 @@ export const ANALYZE_GRAPH_ID = 'analyze_graph'; * Analyzer graph view displayed in the document details expandable flyout left section under the Visualize tab */ export const AnalyzeGraph: FC = () => { - const { eventId } = useLeftPanelContext(); - const scopeId = 'flyout'; // Different scope Id to distinguish flyout and data table analyzers + const { eventId, scopeId } = useLeftPanelContext(); const { from, to, shouldUpdate, selectedPatterns } = useTimelineDataFilters( isActiveTimeline(scopeId) ); @@ -34,7 +33,7 @@ export const AnalyzeGraph: FC = () => {
> = memo(({ path }) => { const { telemetry } = useKibana().services; @@ -42,14 +45,18 @@ export const LeftPanel: FC> = memo(({ path }) => { const { eventId, indexName, scopeId, getFieldsData } = useLeftPanelContext(); const eventKind = getField(getFieldsData('event.kind')); - const tabsDisplayed = useMemo( - () => - eventKind === EventKind.signal - ? [tabs.insightsTab, tabs.investigationTab, tabs.responseTab] - : [tabs.insightsTab], - [eventKind] + const visualizationInFlyoutEnabled = useIsExperimentalFeatureEnabled( + 'visualizationInFlyoutEnabled' ); + const tabsDisplayed = useMemo(() => { + return eventKind !== EventKind.signal + ? EVENT_TABS + : visualizationInFlyoutEnabled + ? [tabs.visualizeTab, ...ALERT_TABS] + : ALERT_TABS; + }, [eventKind, visualizationInFlyoutEnabled]); + const selectedTabId = useMemo(() => { const defaultTab = tabsDisplayed[0].id; if (!path) return defaultTab; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/analyzer_panel.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/analyzer_panel.tsx new file mode 100644 index 00000000000000..24765b8ed3c93b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/analyzer_panel.tsx @@ -0,0 +1,65 @@ +/* + * 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 from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { useSelector } from 'react-redux'; +import { FormattedMessage } from '@kbn/i18n-react'; +import * as selectors from '../../../../resolver/store/selectors'; +import type { State } from '../../../../common/store/types'; +import { PanelRouter } from '../../../../resolver/view/panels'; +import { usePreviewPanelContext } from '../context'; + +/** + * Analyzer side panel on a preview panel + */ +export const AnalyzerPanel: React.FC = () => { + const { scopeId } = usePreviewPanelContext(); + const resolverComponentInstanceID = `flyout-${scopeId}`; + + const isLoading = useSelector((state: State) => + selectors.isTreeLoading(state.analyzer[resolverComponentInstanceID]) + ); + + const hasError = useSelector((state: State) => + selectors.hadErrorLoadingTree(state.analyzer[resolverComponentInstanceID]) + ); + + const resolverTreeHasNodes = useSelector((state: State) => + selectors.resolverTreeHasNodes(state.analyzer[resolverComponentInstanceID]) + ); + + return isLoading ? ( +
+ +
+ ) : hasError ? ( +
+
+ {' '} + +
+
+ ) : resolverTreeHasNodes ? ( + + ) : ( +
+
+ {' '} + +
+
+ ); +}; + +AnalyzerPanel.displayName = 'AnalyzerPanel'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx index 27e7d8646031c9..370f452f45e506 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx @@ -11,9 +11,10 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import type { DocumentDetailsPreviewPanelKey } from '../shared/constants/panel_keys'; import { panels } from './panels'; -export type PreviewPanelPaths = 'rule-preview' | 'alert-reason-preview'; +export type PreviewPanelPaths = 'rule-preview' | 'alert-reason-preview' | 'analyzer-panel'; export const RulePreviewPanel: PreviewPanelPaths = 'rule-preview'; export const AlertReasonPreviewPanel: PreviewPanelPaths = 'alert-reason-preview'; +export const AnalyzerPanel: PreviewPanelPaths = 'analyzer-panel'; export interface PreviewPanelProps extends FlyoutPanelProps { key: typeof DocumentDetailsPreviewPanelKey; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/panels.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/panels.tsx index b5e42754a46d3a..f509fc4b669cc4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/panels.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/panels.tsx @@ -10,6 +10,7 @@ import { AlertReasonPreview } from './components/alert_reason_preview'; import type { PreviewPanelPaths } from '.'; import { RulePreview } from './components/rule_preview'; import { RulePreviewFooter } from './components/rule_preview_footer'; +import { AnalyzerPanel } from './components/analyzer_panel'; export type PreviewPanelType = Array<{ /** @@ -39,4 +40,8 @@ export const panels: PreviewPanelType = [ id: 'alert-reason-preview', content: , }, + { + id: 'analyzer-panel', + content: , + }, ]; diff --git a/x-pack/plugins/security_solution/public/resolver/view/controls/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/controls/index.tsx index 6829f9ece1fe03..4cda7b2192abd7 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/controls/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/controls/index.tsx @@ -28,11 +28,13 @@ import { DateSelectionButton } from './date_picker'; import { StyledGraphControls, StyledGraphControlsColumn, StyledEuiRange } from './styles'; import { NodeLegend } from './legend'; import { SchemaInformation } from './schema'; +import { PanelButton } from './panel'; export const GraphControls = React.memo( ({ id, className, + databaseDocumentID, }: { /** * Id that identify the scope of analyzer @@ -42,20 +44,28 @@ export const GraphControls = React.memo( * A className string provided by `styled` */ className?: string; + /** + * The `_id` for an ES document. Used to select a process that we'll show the graph for. + */ + databaseDocumentID: string; }) => { const dispatch = useDispatch(); const scalingFactor = useSelector((state: State) => selectors.scalingFactor(state.analyzer[id]) ); + const eventIndices = useSelector((state: State) => selectors.eventIndices(state.analyzer[id])); const { timestamp } = useContext(SideEffectContext); const isDatePickerAndSourcererDisabled = useIsExperimentalFeatureEnabled( 'analyzerDatePickersAndSourcererDisabled' ); + const visualizationInFlyoutEnabled = useIsExperimentalFeatureEnabled( + 'visualizationInFlyoutEnabled' + ); const [activePopover, setPopover] = useState< null | 'schemaInfo' | 'nodeLegend' | 'sourcererSelection' | 'datePicker' >(null); const colorMap = useColors(); - + const inFlyout = id.startsWith('flyout'); const setActivePopover = useCallback( (value) => { if (value === activePopover) { @@ -148,6 +158,9 @@ export const GraphControls = React.memo( /> ) : null} + {visualizationInFlyoutEnabled && inFlyout && ( + + )} diff --git a/x-pack/plugins/security_solution/public/resolver/view/controls/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/controls/panel.tsx new file mode 100644 index 00000000000000..f6c278aaa755e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/controls/panel.tsx @@ -0,0 +1,51 @@ +/* + * 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, { useCallback, memo } from 'react'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { StyledEuiButtonIcon } from './styles'; +import { useColors } from '../use_colors'; +import { DocumentDetailsPreviewPanelKey } from '../../../flyout/document_details/shared/constants/panel_keys'; +import { type PreviewPanelProps, AnalyzerPanel } from '../../../flyout/document_details/preview'; + +export const PanelButton = memo( + ({ id, eventId, indexName }: { id: string; eventId: string; indexName: string }) => { + const { openPreviewPanel } = useExpandableFlyoutApi(); + + // If in flyout, scope Id is "flyout-scopeId" + const scopeId = id.startsWith('flyout') ? id.substring(7) : id; + + const onClick = useCallback(() => { + const PreviewPanelAnalyzerPanel: PreviewPanelProps['path'] = { tab: AnalyzerPanel }; + openPreviewPanel({ + id: DocumentDetailsPreviewPanelKey, + path: PreviewPanelAnalyzerPanel, + params: { + id: eventId, + indexName, + scopeId, + }, + }); + }, [openPreviewPanel, eventId, indexName, scopeId]); + const colorMap = useColors(); + + return ( + + ); + } +); + +PanelButton.displayName = 'PanelButton'; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index 4ec2784411d18a..76d8cc51d291cf 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -11,7 +11,7 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiBreadcrumb, EuiBasicTableColumn, EuiSearchBarProps } from '@elastic/eui'; -import { EuiSpacer, EuiText, EuiInMemoryTable } from '@elastic/eui'; +import { EuiSpacer, EuiText, EuiInMemoryTable, EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { StyledPanel } from '../styles'; @@ -72,10 +72,12 @@ export const EventDetail = memo(function EventDetail({ selectors.currentRelatedEventData(state.analyzer[id]) ); + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; + return isLoading ? ( - + - + ) : event ? ( ) : ( - + - + ); }); @@ -120,9 +122,10 @@ const EventDetailContents = memo(function ({ }); const nodeName = processEvent ? eventModel.processNameSafeVersion(processEvent) : null; + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; return ( - + - + ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx index 60fc1b4ccaddd6..6056bbfc091de9 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx @@ -17,6 +17,7 @@ import { EuiTextColor, EuiLink, EuiInMemoryTable, + EuiPanel, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; @@ -59,19 +60,20 @@ export const NodeDetail = memo(function ({ id, nodeID }: { id: string; nodeID: s const nodeStatus = useSelector((state: State) => selectors.nodeDataStatus(state.analyzer[id])(nodeID) ); + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; return nodeStatus === 'loading' ? ( - + - + ) : processEvent ? ( - + - + ) : ( - + - + ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events.tsx index 99d1a5aed9fd8b..49f4534abc93bc 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events.tsx @@ -8,7 +8,7 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiButtonEmpty, EuiSpacer, EuiInMemoryTable, EuiToolTip } from '@elastic/eui'; +import { EuiButtonEmpty, EuiSpacer, EuiInMemoryTable, EuiToolTip, EuiPanel } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useSelector } from 'react-redux'; import { FormattedCount } from '../../../common/components/formatted_number'; @@ -27,16 +27,17 @@ export function NodeEvents({ id, nodeID }: { id: string; nodeID: string }) { nodeDataModel.firstEvent(selectors.nodeDataForID(state.analyzer[id])(nodeID)) ); const nodeStats = useSelector((state: State) => selectors.nodeStats(state.analyzer[id])(nodeID)); + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; if (processEvent === undefined || nodeStats === undefined) { return ( - + - + ); } else { return ( - + - + ); } } diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx index a519b5dc5bfb40..3efeefd51ad50f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx @@ -15,6 +15,7 @@ import { EuiFlexItem, EuiButton, EuiCallOut, + EuiPanel, } from '@elastic/eui'; import { useSelector, useDispatch } from 'react-redux'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -52,15 +53,18 @@ export const NodeEventsInCategory = memo(function ({ const hasError = useSelector((state: State) => selectors.hadErrorLoadingNodeEventsInCategory(state.analyzer[id]) ); - + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; return ( <> {isLoading ? ( - + - + ) : ( - + {hasError || !node ? ( )} - + )} ); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx index 34e9d5a6e34157..88041fee03a0c0 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx @@ -10,7 +10,7 @@ import { useDispatch, useSelector } from 'react-redux'; import React, { memo, useMemo, useCallback, useContext } from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiBadge, EuiButtonEmpty, EuiSpacer, EuiInMemoryTable } from '@elastic/eui'; +import { EuiBadge, EuiButtonEmpty, EuiSpacer, EuiInMemoryTable, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SideEffectContext } from '../side_effect_context'; import { StyledPanel } from '../styles'; @@ -122,8 +122,11 @@ export const NodeList = memo(({ id }: { id: string }) => { ); const showWarning = children === true || ancestors === true || generations === true; const rowProps = useMemo(() => ({ 'data-test-subj': 'resolver:node-list:item' }), []); + + const PanelWrapper = id.startsWith('flyout') ? EuiPanel : StyledPanel; + return ( - + {showWarning && } @@ -134,7 +137,7 @@ export const NodeList = memo(({ id }: { id: string }) => { columns={columns} sorting /> - + ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx index d9ec54cdd046dd..18048f430162ab 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx @@ -162,12 +162,14 @@ export const ResolverWithoutProviders = React.memo( ); })} - + {!resolverComponentInstanceID.startsWith('flyout') && ( + + )} ) : ( )} - + );