/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { useState } from 'react'; import { CorrelationFinding } from '../../../../../types'; import { ruleTypes } from '../../../Rules/utils/constants'; import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../../utils/constants'; import { EuiButton, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText, EuiPanel, EuiInMemoryTable, EuiBasicTableColumn, EuiPopover, } from '@elastic/eui'; import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter'; import { FindingItemType } from '../../containers/Findings/Findings'; import { RouteComponentProps } from 'react-router-dom'; import { DataStore } from '../../../../store/DataStore'; import { capitalizeFirstLetter, formatRuleType, getSeverityBadge } from '../../../../utils/helpers'; import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; export interface CorrelationsTableProps { finding: FindingItemType; correlatedFindings: CorrelationFinding[]; history: RouteComponentProps['history']; isLoading: boolean; filterOptions: { logTypes: Set; ruleSeverity: Set; }; } export const CorrelationsTable: React.FC = ({ correlatedFindings, finding, history, isLoading, filterOptions: { logTypes, ruleSeverity }, }) => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ [key: string]: JSX.Element; }>({}); const [findingIdCopied, setFindingIdCopied] = useState(false); const [copyPopoverTimeout, setCopyPopoverTimeout] = useState(undefined); const toggleCorrelationDetails = (item: CorrelationFinding) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; if (itemIdToExpandedRowMapValues[item.id]) { delete itemIdToExpandedRowMapValues[item.id]; } else { itemIdToExpandedRowMapValues[item.id] = ( Finding ID {item.id} Threat detector {item.detectorName} Detection rule {item.detectionRule?.name || '-'} ); } setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; const actions = [ { render: (item: CorrelationFinding) => ( toggleCorrelationDetails(item)} aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} /> ), }, ]; if (window.navigator?.clipboard) { const copyFindingIdToClipboard = (findingId: string) => { try { window.navigator.clipboard.writeText(findingId); setFindingIdCopied(true); window.clearTimeout(copyPopoverTimeout); setCopyPopoverTimeout( window.setTimeout(() => { setFindingIdCopied(false); }, 1000) ); } catch (error: any) { console.error('Failed to copy id'); } }; actions.unshift({ render: (item: CorrelationFinding) => ( copyFindingIdToClipboard(item.id)} aria-label="Copy" iconType="copy" /> } isOpen={findingIdCopied} closePopover={() => setFindingIdCopied(false)} anchorPosition="upCenter" > Finding id copied ), }); } const columns: EuiBasicTableColumn[] = [ { field: 'timestamp', name: 'Time', sortable: true, }, { name: 'Correlated rule', truncateText: true, render: (item: CorrelationFinding) => item?.correlationRule?.name || DEFAULT_EMPTY_DATA, }, { field: 'logType', name: 'Log type', sortable: true, render: (category: string) => // TODO: This formatting may need some refactoring depending on the response payload ruleTypes.find((ruleType) => ruleType.value === category)?.label || DEFAULT_EMPTY_DATA, }, { name: 'Rule severity', field: 'detectionRule.severity', truncateText: true, sortable: true, align: 'left', render: (severity: string) => getSeverityBadge(severity), }, { field: 'correlationScore', name: 'Score', sortable: true, }, { name: 'Actions', actions, isExpander: true, }, ]; const search = { box: { placeholder: 'Search findings', schema: true, }, filters: [ { type: 'field_value_selection', field: 'detectionRule.severity', name: 'Rule severity', options: Array.from(ruleSeverity).map((severity) => { const name = parseAlertSeverityToOption(severity)?.label || capitalizeFirstLetter(severity); return { value: severity, name: name || severity }; }), multiSelect: 'or', } as FieldValueSelectionFilterConfigType, { type: 'field_value_selection', field: 'logType', name: 'Log type', options: Array.from(logTypes).map((type) => ({ value: type, name: formatRuleType(type), })), multiSelect: 'or', } as FieldValueSelectionFilterConfigType, ], }; const goToCorrelationsPage = () => { DataStore.findings.closeFlyout(); history.push({ pathname: `${ROUTES.CORRELATIONS}`, state: { finding: finding, correlatedFindings: correlatedFindings, }, }); }; return ( <>

Correlated findings

goToCorrelationsPage()} disabled={correlatedFindings.length === 0} > View correlations graph
); };