From 87c2b874c163bd06e83134e6b7eaae16942cd270 Mon Sep 17 00:00:00 2001 From: vikhy-aws <191836418+vikhy-aws@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:50:34 -0800 Subject: [PATCH] seperate out table-view into a different file Signed-off-by: vikhy-aws <191836418+vikhy-aws@users.noreply.github.com> --- .../containers/CorrelationsContainer.tsx | 240 +------------- .../containers/CorrelationsTable.tsx | 6 +- .../containers/CorrelationsTableView.tsx | 302 ++++++++++++++++++ public/pages/Correlations/utils/constants.tsx | 4 +- 4 files changed, 323 insertions(+), 229 deletions(-) create mode 100644 public/pages/Correlations/containers/CorrelationsTableView.tsx diff --git a/public/pages/Correlations/containers/CorrelationsContainer.tsx b/public/pages/Correlations/containers/CorrelationsContainer.tsx index bf5f4a61..f03ecc2c 100644 --- a/public/pages/Correlations/containers/CorrelationsContainer.tsx +++ b/public/pages/Correlations/containers/CorrelationsContainer.tsx @@ -42,7 +42,6 @@ import { EuiHorizontalRule, EuiButtonGroup, EuiFieldSearch, - EuiLoadingChart, } from '@elastic/eui'; import { FilterItem, FilterGroup } from '../components/FilterGroup'; import { @@ -61,18 +60,11 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { Network } from 'react-graph-vis'; import { getLogTypeLabel } from '../../LogTypes/utils/helpers'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import { - errorNotificationToast, - renderVisualization, - setBreadcrumbs, -} from '../../../utils/helpers'; +import { errorNotificationToast, setBreadcrumbs } from '../../../utils/helpers'; import { PageHeader } from '../../../components/PageHeader/PageHeader'; -import { ChartContainer } from '../../../components/Charts/ChartContainer'; -import { getChartTimeUnit, getDomainRange } from '../../Overview/utils/helpers'; import { debounce } from 'lodash'; -import { CorrelationsTable } from './CorrelationsTable'; -import { CorrelationsTableFlyout } from './CorrelationsTableFlyout'; -import { getCorrelatedFindingsVisualizationSpec, mapConnectedCorrelations } from '../utils/helpers'; +import { CorrelationsTableView } from './CorrelationsTableView'; +import { mapConnectedCorrelations } from '../utils/helpers'; interface CorrelationsProps extends RouteComponentProps< @@ -115,10 +107,7 @@ interface CorrelationsState { isGraphView: boolean; correlationsTableData: CorrelationsTableData[]; connectedFindings: CorrelationFinding[][]; - isFlyoutOpen: boolean; - selectedTableRow: CorrelationsTableData | null; searchTerm: string; - flyoutGraphData: CorrelationGraphData; } export class Correlations extends React.Component { @@ -137,10 +126,7 @@ export class Correlations extends React.Component { - let newGraphData: CorrelationGraphData = { - graph: { - nodes: [], - edges: [], - }, - events: { - click: this.onNodeClick, - }, - }; - - if (correlationTableRow.correlatedFindings) { - const correlationPairs = this.getCorrelationPairs(correlationTableRow.correlatedFindings); - newGraphData = this.prepareGraphData(correlationPairs); - } - - // Set all required state at once - this.setState({ - isFlyoutOpen: true, - selectedTableRow: correlationTableRow, - flyoutGraphData: newGraphData, - }); - }; - - private closeTableFlyout = () => { - this.setState({ - isFlyoutOpen: false, - selectedTableRow: null, - flyoutGraphData: { - graph: { nodes: [], edges: [] }, - events: { click: this.onNodeClick }, - }, - }); - }; - onFindingInspect = async (id: string, logType: string) => { // get finding data and set the specificFindingInfo const specificFindingInfo = await DataStore.correlations.getCorrelatedFindings(id, logType); @@ -687,102 +638,6 @@ export class Correlations extends React.Component { - const visData = connectedFindings.map((correlatedFindings) => { - return { - title: 'Correlated Findings', - correlatedFinding: correlatedFindings.length, - time: correlatedFindings[0].timestamp, - }; - }); - - const { - dateTimeFilter = { - startTime: DEFAULT_DATE_RANGE.start, - endTime: DEFAULT_DATE_RANGE.end, - }, - } = this.props; - - const chartTimeUnits = getChartTimeUnit(dateTimeFilter.startTime, dateTimeFilter.endTime); - - return getCorrelatedFindingsVisualizationSpec(visData, { - timeUnit: chartTimeUnits.timeUnit, - dateFormat: chartTimeUnits.dateFormat, - domain: getDomainRange( - [dateTimeFilter.startTime, dateTimeFilter.endTime], - chartTimeUnits.timeUnit.unit - ), - }); - }; - - private renderCorrelatedFindingsChart = () => { - renderVisualization( - this.generateVisualizationSpec(this.state.connectedFindings), - 'correlated-findings-view' - ); - - return ( - <> - - - - - - -

Correlated Findings

-
-
-
-
- - - -
-
- - - ); - }; - - private getFilteredTableData = (tableData: CorrelationsTableData[]): CorrelationsTableData[] => { - const { logTypeFilterOptions, severityFilterOptions } = this.state; - const alertSeverityMap: { [key: string]: string } = { - '1': 'critical', - '2': 'high', - '3': 'medium', - '4': 'low', - '5': 'informational', - }; - - const selectedLogTypes = logTypeFilterOptions - .filter((item) => item.checked === 'on' && item.visible) - .map((item) => item.id); - - const selectedSeverities = severityFilterOptions - .filter((item) => item.checked === 'on' && item.visible) - .map((item) => item.id.toLowerCase()); - - return tableData.filter((row) => { - const logTypeMatch = row.logTypes.some((logType) => selectedLogTypes.includes(logType)); - - const severityMatch = row.alertSeverity.some((severity) => - selectedSeverities.includes(alertSeverityMap[severity]) - ); - - const searchLower = this.state.searchTerm.toLowerCase(); - const searchMatch = - this.state.searchTerm === '' || - row.correlationRule?.toLowerCase().includes(searchLower) || - row.logTypes.some((type) => type.toLowerCase().includes(searchLower)) || - row.alertSeverity.some((severity) => - alertSeverityMap[severity].toLowerCase().includes(searchLower) - ) || - row.findingsSeverity.some((severity) => severity.toLowerCase().includes(searchLower)) || - row.resources.some((resource) => resource.toLowerCase().includes(searchLower)); - return logTypeMatch && severityMatch && searchMatch; - }); - }; - private debouncedSearch = debounce((searchTerm: string) => { this.setState({ searchTerm }); }, 300); @@ -806,70 +661,6 @@ export class Correlations extends React.Component { - if (loadingData) { - return ( -
- -
- ); - } - - const filteredTableData = this.getFilteredTableData(this.state.correlationsTableData); - - return ( - <> - - - ); - }; - - private prepareGraphData = (correlationPairs: CorrelationFinding[][] | [any, any][]) => { - const createdEdges = new Set(); - const createdNodes = new Set(); - const graphData: CorrelationGraphData = { - graph: { - nodes: [], - edges: [], - }, - events: { - click: this.onNodeClick, - }, - }; - - correlationPairs.forEach((correlation: CorrelationFinding[]) => { - const possibleCombination1 = `${correlation[0].id}:${correlation[1].id}`; - const possibleCombination2 = `${correlation[1].id}:${correlation[0].id}`; - - if (createdEdges.has(possibleCombination1) || createdEdges.has(possibleCombination2)) { - return; - } - - if (!createdNodes.has(correlation[0].id)) { - this.addNode(graphData.graph.nodes, correlation[0]); - createdNodes.add(correlation[0].id); - } - if (!createdNodes.has(correlation[1].id)) { - this.addNode(graphData.graph.nodes, correlation[1]); - createdNodes.add(correlation[1].id); - } - this.addEdge(graphData.graph.edges, correlation[0], correlation[1]); - createdEdges.add(possibleCombination1); - }); - - return graphData; - }; - - private getCorrelationPairs = (correlatedFindings: any[]) => { - const pairs: [any, any][] = []; - for (let i = 0; i < correlatedFindings.length; i++) { - for (let j = i + 1; j < correlatedFindings.length; j++) { - pairs.push([correlatedFindings[i], correlatedFindings[j]]); - } - } - return pairs; - }; - render() { const findingCardsData = this.state.specificFindingInfo; const datePicker = ( @@ -1025,23 +816,24 @@ export class Correlations extends React.Component - {this.renderCorrelatedFindingsChart()} - {this.renderCorrelationsTable(this.state.loadingTableData)} + )} - {this.state.isFlyoutOpen && ( - - )} ); } diff --git a/public/pages/Correlations/containers/CorrelationsTable.tsx b/public/pages/Correlations/containers/CorrelationsTable.tsx index c7b57c0b..b8bcbff6 100644 --- a/public/pages/Correlations/containers/CorrelationsTable.tsx +++ b/public/pages/Correlations/containers/CorrelationsTable.tsx @@ -14,12 +14,12 @@ import { displayBadges, displaySeveritiesBadges, displayResourcesBadges } from ' import { DEFAULT_EMPTY_DATA } from '../../../utils/constants'; interface CorrelationsTableProps { - tableData: CorrelationsTableData[]; + correlationsTableData: CorrelationsTableData[]; onViewDetails: (row: CorrelationsTableData) => void; } export const CorrelationsTable: React.FC = ({ - tableData, + correlationsTableData, onViewDetails, }) => { const alertSeverityMap: { [key: string]: string } = { @@ -99,7 +99,7 @@ export const CorrelationsTable: React.FC = ({ return ( void; + addEdge: (edges: any[], f1: CorrelationFinding, f2: CorrelationFinding) => void; + onNodeClick: (params: any) => void; + setNetwork: (network: Network) => void; + createNodeTooltip: ({ detectionRule, timestamp, logType }: CorrelationFinding) => Element | null; +} + +interface CorrelationsTableViewState { + isFlyoutOpen: boolean; + selectedTableRow: CorrelationsTableData | null; + flyoutGraphData: CorrelationGraphData; +} + +export class CorrelationsTableView extends React.Component< + CorrelationsTableViewProps, + CorrelationsTableViewState +> { + static defaultProps = { + dateTimeFilter: { + startTime: DEFAULT_DATE_RANGE.start, + endTime: DEFAULT_DATE_RANGE.end, + }, + }; + + constructor(props: CorrelationsTableViewProps) { + super(props); + this.state = { + isFlyoutOpen: false, + selectedTableRow: null, + flyoutGraphData: { ...emptyGraphData }, + }; + } + + private getCorrelationPairs = (correlatedFindings: any[]) => { + const pairs: [any, any][] = []; + for (let i = 0; i < correlatedFindings.length; i++) { + for (let j = i + 1; j < correlatedFindings.length; j++) { + pairs.push([correlatedFindings[i], correlatedFindings[j]]); + } + } + return pairs; + }; + + private prepareGraphData = (correlationPairs: CorrelationFinding[][] | [any, any][]) => { + const createdEdges = new Set(); + const createdNodes = new Set(); + const graphData: CorrelationGraphData = { + graph: { + nodes: [], + edges: [], + }, + events: { + click: this.props.onNodeClick, + }, + }; + + correlationPairs.forEach((correlation: CorrelationFinding[]) => { + const possibleCombination1 = `${correlation[0].id}:${correlation[1].id}`; + const possibleCombination2 = `${correlation[1].id}:${correlation[0].id}`; + + if (createdEdges.has(possibleCombination1) || createdEdges.has(possibleCombination2)) { + return; + } + + if (!createdNodes.has(correlation[0].id)) { + this.props.addNode(graphData.graph.nodes, correlation[0]); + createdNodes.add(correlation[0].id); + } + if (!createdNodes.has(correlation[1].id)) { + this.props.addNode(graphData.graph.nodes, correlation[1]); + createdNodes.add(correlation[1].id); + } + this.props.addEdge(graphData.graph.edges, correlation[0], correlation[1]); + createdEdges.add(possibleCombination1); + }); + + return graphData; + }; + + private openTableFlyout = (correlationTableRow: CorrelationsTableData) => { + let newGraphData: CorrelationGraphData = { + graph: { + nodes: [], + edges: [], + }, + events: { + click: this.props.onNodeClick, + }, + }; + if (correlationTableRow.correlatedFindings) { + const correlationPairs = this.getCorrelationPairs(correlationTableRow.correlatedFindings); + newGraphData = this.prepareGraphData(correlationPairs); + } + this.setState({ + isFlyoutOpen: true, + selectedTableRow: correlationTableRow, + flyoutGraphData: newGraphData, + }); + }; + + private renderCorrelationsTable = (loadingData: boolean) => { + if (loadingData) { + return ( +
+ +
+ ); + } + const filteredTableData = this.getFilteredTableData(this.props.correlationsTableData); + return ( + <> + + + ); + }; + + private renderCorrelatedFindingsChart = () => { + renderVisualization( + this.generateVisualizationSpec(this.props.connectedFindings), + 'correlated-findings-view' + ); + return this.props.connectedFindings.length > 0 ? ( + <> + + + + + + +

Correlated Findings

+
+
+
+
+ + + +
+
+ + + ) : ( + +

No correlations found

+ + } + body={ + +

There are no correlated findings in the system.

+
+ } + actions={[ + + Create correlation rule + , + ]} + /> + ); + }; + + private getFilteredTableData = (tableData: CorrelationsTableData[]): CorrelationsTableData[] => { + const { logTypeFilterOptions, severityFilterOptions, searchTerm } = this.props; + const alertSeverityMap: { [key: string]: string } = { + '1': 'critical', + '2': 'high', + '3': 'medium', + '4': 'low', + '5': 'informational', + }; + + const selectedLogTypes = logTypeFilterOptions + .filter((item) => item.checked === 'on' && item.visible) + .map((item) => item.id); + + const selectedSeverities = severityFilterOptions + .filter((item) => item.checked === 'on' && item.visible) + .map((item) => item.id.toLowerCase()); + + return tableData.filter((row) => { + const logTypeMatch = row.logTypes.some((logType) => selectedLogTypes.includes(logType)); + + const severityMatch = row.alertSeverity.some((severity) => + selectedSeverities.includes(alertSeverityMap[severity]) + ); + + const searchLower = searchTerm.toLowerCase(); + const searchMatch = + searchTerm === '' || + row.correlationRule?.toLowerCase().includes(searchLower) || + row.logTypes.some((type) => type.toLowerCase().includes(searchLower)) || + row.alertSeverity.some((severity) => + alertSeverityMap[severity].toLowerCase().includes(searchLower) + ) || + row.findingsSeverity.some((severity) => severity.toLowerCase().includes(searchLower)) || + row.resources.some((resource) => resource.toLowerCase().includes(searchLower)); + return logTypeMatch && severityMatch && searchMatch; + }); + }; + + private generateVisualizationSpec = (connectedFindings: CorrelationFinding[][]) => { + const visData = connectedFindings.map((correlatedFindings) => { + return { + title: 'Correlated Findings', + correlatedFinding: correlatedFindings.length, + time: correlatedFindings[0].timestamp, + }; + }); + + const { dateTimeFilter } = this.props; + const chartTimeUnits = getChartTimeUnit( + dateTimeFilter?.startTime || DEFAULT_DATE_RANGE.start, + dateTimeFilter?.endTime || DEFAULT_DATE_RANGE.end + ); + + return getCorrelatedFindingsVisualizationSpec(visData, { + timeUnit: chartTimeUnits.timeUnit, + dateFormat: chartTimeUnits.dateFormat, + domain: getDomainRange( + [ + dateTimeFilter?.startTime || DEFAULT_DATE_RANGE.start, + dateTimeFilter?.endTime || DEFAULT_DATE_RANGE.end, + ], + chartTimeUnits.timeUnit.unit + ), + }); + }; + + private closeTableFlyout = () => { + this.setState({ + isFlyoutOpen: false, + selectedTableRow: null, + flyoutGraphData: { + graph: { nodes: [], edges: [] }, + events: { click: this.props.onNodeClick }, + }, + }); + }; + + render() { + return ( + <> + {this.renderCorrelatedFindingsChart()} + {this.renderCorrelationsTable(this.props.loadingTableData)} + {this.state.isFlyoutOpen && ( + + )} + + ); + } +} diff --git a/public/pages/Correlations/utils/constants.tsx b/public/pages/Correlations/utils/constants.tsx index 08ad33c2..744d4f6a 100644 --- a/public/pages/Correlations/utils/constants.tsx +++ b/public/pages/Correlations/utils/constants.tsx @@ -36,10 +36,10 @@ export const graphRenderOptions = { enabled: true, barnesHut: { gravitationalConstant: -7000, - centralGravity: 0.9, + centralGravity: 0.5, springConstant: 0.01, springLength: 125, - damping: 0.07, + damping: 0.1, }, stabilization: { enabled: true,