From bb16e09e71f2d8a8248f5f60a5fc3b9158e88de3 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 18:10:20 -0700 Subject: [PATCH] UX updates for correlations (#619) (#635) (#642) * minor UX updates Signed-off-by: Amardeepsingh Siglani * ux improvements for correlations Signed-off-by: Amardeepsingh Siglani * made more refactors for ux polish Signed-off-by: Amardeepsingh Siglani * updated snapshots Signed-off-by: Amardeepsingh Siglani * addressed comments in PR Signed-off-by: Amardeepsingh Siglani --------- Signed-off-by: Amardeepsingh Siglani (cherry picked from commit 205ee1890d24d412e24fa830358a0a567118e00b) Co-authored-by: Amardeepsingh Siglani (cherry picked from commit 65ea940610efa63e9557509d40212c5ed194098b) Co-authored-by: opensearch-trigger-bot[bot] <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> --- .../components/CorrelationGraph.tsx | 2 +- .../components/ExperimentalBanner.tsx | 7 +- .../Correlations/components/FilterGroup.tsx | 9 +- .../Correlations/components/FindingCard.tsx | 39 +++--- .../containers/CorrelationsContainer.tsx | 48 ++++--- public/pages/Correlations/utils/constants.tsx | 9 +- public/pages/Correlations/utils/helpers.tsx | 7 +- .../DetectorRulesView.test.tsx.snap | 100 ++++++++++++--- .../DetectorDetails.test.tsx.snap | 100 ++++++++++++--- .../DetectorDetailsView.test.tsx.snap | 100 ++++++++++++--- .../CorrelationsTable/CorrelationsTable.tsx | 121 +++++++++++++++--- .../components/FindingDetailsFlyout.tsx | 16 ++- .../FindingsTable/FindingsTable.tsx | 19 ++- public/pages/Rules/utils/constants.ts | 42 +++++- public/pages/Rules/utils/helpers.tsx | 9 +- public/store/CorrelationsStore.ts | 6 +- public/utils/helpers.tsx | 12 +- types/Correlations.ts | 4 +- 18 files changed, 499 insertions(+), 151 deletions(-) diff --git a/public/pages/Correlations/components/CorrelationGraph.tsx b/public/pages/Correlations/components/CorrelationGraph.tsx index ec6a836ee..4de34e6fc 100644 --- a/public/pages/Correlations/components/CorrelationGraph.tsx +++ b/public/pages/Correlations/components/CorrelationGraph.tsx @@ -37,7 +37,7 @@ export const CorrelationGraph: React.FC = ({ graph={{ nodes, edges }} options={options} events={events} - style={{ border: '1px solid', backgroundColor: '#ffffff' }} + style={{ backgroundColor: '#ffffff' }} getNetwork={getNetwork} /> ); diff --git a/public/pages/Correlations/components/ExperimentalBanner.tsx b/public/pages/Correlations/components/ExperimentalBanner.tsx index 9c24a8f23..03c98dd81 100644 --- a/public/pages/Correlations/components/ExperimentalBanner.tsx +++ b/public/pages/Correlations/components/ExperimentalBanner.tsx @@ -14,15 +14,16 @@ export const CorrelationsExperimentalBanner = () => { The feature is experimental and should not be used in a production environment. While we are working on the finishing touches, share your ideas and feedback on  - forum.opensearch.org. + forum.opensearch.org - For more information see   + . For more information see   - Security Analytics Documentation. + Security Analytics Documentation + .

diff --git a/public/pages/Correlations/components/FilterGroup.tsx b/public/pages/Correlations/components/FilterGroup.tsx index 5cb3defc7..b71a15eaa 100644 --- a/public/pages/Correlations/components/FilterGroup.tsx +++ b/public/pages/Correlations/components/FilterGroup.tsx @@ -23,6 +23,7 @@ export interface LogTypeFilterGroupProps { export const FilterGroup: React.FC = ({ groupName, items, setItems }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [showActiveFilters, setShowActiveFilters] = useState(false); const onButtonClick = () => { setIsPopoverOpen(!isPopoverOpen); @@ -55,6 +56,7 @@ export const FilterGroup: React.FC = ({ groupName, item } setItems(newItems); + setShowActiveFilters(true); } const button = ( @@ -62,9 +64,10 @@ export const FilterGroup: React.FC = ({ groupName, item iconType="arrowDown" onClick={onButtonClick} isSelected={isPopoverOpen} - numFilters={items.length} - hasActiveFilters={!!items.find((item) => item.checked === 'on')} - numActiveFilters={items.filter((item) => item.checked === 'on').length} + hasActiveFilters={showActiveFilters && !!items.find((item) => item.checked === 'on')} + numActiveFilters={ + showActiveFilters ? items.filter((item) => item.checked === 'on').length : undefined + } > {groupName} diff --git a/public/pages/Correlations/components/FindingCard.tsx b/public/pages/Correlations/components/FindingCard.tsx index e3404c9ef..6e597606d 100644 --- a/public/pages/Correlations/components/FindingCard.tsx +++ b/public/pages/Correlations/components/FindingCard.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useCallback } from 'react'; +import React from 'react'; import { EuiPanel, EuiFlexGroup, @@ -14,8 +14,8 @@ import { EuiBadge, EuiHorizontalRule, EuiToolTip, + EuiDescriptionList, } from '@elastic/eui'; -import { rulePriorityBySeverity } from '../../CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRulesTable'; import { getAbbrFromLogType, getSeverityLabel, @@ -32,7 +32,7 @@ export interface FindingCardProps { detectionRule: { name: string; severity: string }; correlationData?: { // ruleName: string; - score: number; + score: string; onInspect: (findingId: string, logType: string) => void; }; finding: CorrelationFinding; @@ -76,24 +76,22 @@ export const FindingCard: React.FC = ({ ) : null; - const createTextRow = useCallback((label: string, value: string) => { - return ( - - - {label} - - - {value} - - - ); - }, []); + const list = [ + { + title: 'Generated', + description: timestamp, + }, + { + title: 'Detection rule', + description: detectionRule.name, + }, + ]; return ( {correlationHeader} - +
= ({ fontSize: 10, lineHeight: '35px', textAlign: 'center', + borderColor: '#98A2B3', + color: '#98A2B3', }} > {getAbbrFromLogType(logType)} @@ -115,10 +115,10 @@ export const FindingCard: React.FC = ({ position: 'absolute', transform: 'translateY(-100%)', left: '33px', + color: getSeverityColor(detectionRule.severity).text, }} - color={getSeverityColor(detectionRule.severity)} + color={getSeverityColor(detectionRule.severity).background} > - {rulePriorityBySeverity[detectionRule.severity]}{' '} {getSeverityLabel(detectionRule.severity)} ) : null} @@ -142,8 +142,7 @@ export const FindingCard: React.FC = ({ {correlationHeader ? : null} - {createTextRow('Generated', timestamp)} - {createTextRow('Detection rule', detectionRule.name)} + ); }; diff --git a/public/pages/Correlations/containers/CorrelationsContainer.tsx b/public/pages/Correlations/containers/CorrelationsContainer.tsx index 22cab1abc..425f5b416 100644 --- a/public/pages/Correlations/containers/CorrelationsContainer.tsx +++ b/public/pages/Correlations/containers/CorrelationsContainer.tsx @@ -32,6 +32,7 @@ import { EuiEmptyPrompt, EuiButton, EuiBadge, + EuiFilterGroup, } from '@elastic/eui'; import { CorrelationsExperimentalBanner } from '../components/ExperimentalBanner'; import { FilterItem, FilterGroup } from '../components/FilterGroup'; @@ -251,6 +252,8 @@ export class Correlations extends React.Component${getAbbrFromLogType( @@ -259,7 +262,15 @@ export class Correlations extends React.Component { + const { text, background } = getSeverityColor(detectionRule.severity); const tooltipContent = (
- + {getSeverityLabel(detectionRule.severity)} @@ -457,7 +471,7 @@ export class Correlations extends React.Component - - - - + - + + + + Reset filters @@ -529,7 +543,7 @@ export class Correlations extends React.Component ( - {sev.value} + {sev.value} ))} diff --git a/public/pages/Correlations/utils/constants.tsx b/public/pages/Correlations/utils/constants.tsx index e51b09aa9..0b95f4f44 100644 --- a/public/pages/Correlations/utils/constants.tsx +++ b/public/pages/Correlations/utils/constants.tsx @@ -62,7 +62,7 @@ export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(rule return { name: (

- {`${sev.priority} ${sev.name}`} + {`${sev.name}`}

), id: sev.value, @@ -92,5 +92,10 @@ export const getSeverityLabel = (sev: string) => { }; export const getSeverityColor = (sev: string) => { - return ruleSeverity.find((severity) => severity.value === sev)?.color || 'black'; + return ( + ruleSeverity.find((severity) => severity.value === sev.toLowerCase())?.color || { + background: 'white', + text: 'black', + } + ); }; diff --git a/public/pages/Correlations/utils/helpers.tsx b/public/pages/Correlations/utils/helpers.tsx index f8b4a13c1..b06d07ecf 100644 --- a/public/pages/Correlations/utils/helpers.tsx +++ b/public/pages/Correlations/utils/helpers.tsx @@ -14,6 +14,7 @@ import { import { Search } from '@opensearch-project/oui/src/eui_components/basic_table'; import { ruleTypes } from '../../Rules/utils/constants'; import { FieldClause } from '@opensearch-project/oui/src/eui_components/search_bar/query/ast'; +import { formatRuleType } from '../../../utils/helpers'; export const getCorrelationRulesTableColumns = ( onRuleNameClick: (rule: CorrelationRule) => void, @@ -32,7 +33,9 @@ export const getCorrelationRulesTableColumns = ( { name: 'Log types', render: (ruleItem: CorrelationRule) => { - const badges = [...new Set(ruleItem.queries?.map((query) => query.logType))]; + const badges = [ + ...new Set(ruleItem.queries?.map((query) => formatRuleType(query.logType))), + ]; return ( <> {badges.map((badge) => ( @@ -78,7 +81,7 @@ export const getCorrelationRulesTableSearchConfig = ( ): Search => { return { box: { - placeholder: 'Search by rule name, log type?', + placeholder: 'Search by rule name, log type', }, onChange: (args: ArgsWithQuery | ArgsWithError) => { if (!args.error) { diff --git a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap index a4866d062..7ef9d7164 100644 --- a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap +++ b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap @@ -479,31 +479,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -626,31 +641,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -845,31 +875,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -1108,31 +1153,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", diff --git a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap index b0f21ba2a..94f391386 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -3034,31 +3034,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -3181,31 +3196,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -3400,31 +3430,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -3663,31 +3708,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", diff --git a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap index 6a83a71f9..c9062d0ba 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -1858,31 +1858,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -2005,31 +2020,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -2224,31 +2254,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", @@ -2487,31 +2532,46 @@ exports[` spec renders the component 1`] = ` "name": "Rule severity", "options": Array [ Object { - "color": "#cc5642", + "color": Object { + "background": "#cc5642", + "text": "white", + }, "name": "Critical", "priority": "1", "value": "critical", }, Object { - "color": "#e7664c", + "color": Object { + "background": "#e7664c", + "text": "black", + }, "name": "High", "priority": "2", "value": "high", }, Object { - "color": "#d6bf57", + "color": Object { + "background": "#d6bf57", + "text": "black", + }, "name": "Medium", "priority": "3", "value": "medium", }, Object { - "color": "#54b399", + "color": Object { + "background": "#54b399", + "text": "black", + }, "name": "Low", "priority": "4", "value": "low", }, Object { - "color": "#209280", + "color": Object { + "background": "#209280", + "text": "white", + }, "name": "Informational", "priority": "5", "value": "informational", diff --git a/public/pages/Findings/components/CorrelationsTable/CorrelationsTable.tsx b/public/pages/Findings/components/CorrelationsTable/CorrelationsTable.tsx index 90783f294..c94a7e819 100644 --- a/public/pages/Findings/components/CorrelationsTable/CorrelationsTable.tsx +++ b/public/pages/Findings/components/CorrelationsTable/CorrelationsTable.tsx @@ -7,7 +7,6 @@ import React, { useState } from 'react'; import { CorrelationFinding } from '../../../../../types'; import { ruleTypes } from '../../../Rules/utils/constants'; import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../../utils/constants'; -import { getSeverityBadge } from '../../../Rules/utils/helpers'; import { EuiButton, EuiButtonIcon, @@ -18,17 +17,24 @@ import { EuiPanel, EuiInMemoryTable, EuiBasicTableColumn, + EuiPopover, } from '@elastic/eui'; -import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; +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 = ({ @@ -36,12 +42,15 @@ export const CorrelationsTable: React.FC = ({ 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: any) => { + const toggleCorrelationDetails = (item: CorrelationFinding) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; if (itemIdToExpandedRowMapValues[item.id]) { delete itemIdToExpandedRowMapValues[item.id]; @@ -87,6 +96,54 @@ export const CorrelationsTable: React.FC = ({ 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', @@ -96,7 +153,7 @@ export const CorrelationsTable: React.FC = ({ { name: 'Correlated rule', truncateText: true, - render: (item: CorrelationFinding) => item?.correlationRule.name || DEFAULT_EMPTY_DATA, + render: (item: CorrelationFinding) => item?.correlationRule?.name || DEFAULT_EMPTY_DATA, }, { field: 'logType', @@ -108,9 +165,11 @@ export const CorrelationsTable: React.FC = ({ }, { name: 'Rule severity', + field: 'detectionRule.severity', truncateText: true, - align: 'center', - render: (item: CorrelationFinding) => getSeverityBadge(item.detectionRule.severity), + sortable: true, + align: 'left', + render: (severity: string) => getSeverityBadge(severity), }, { field: 'correlationScore', @@ -118,19 +177,42 @@ export const CorrelationsTable: React.FC = ({ sortable: true, }, { - align: RIGHT_ALIGNMENT, - width: '40px', + name: 'Actions', + actions, isExpander: true, - render: (item: any) => ( - toggleCorrelationDetails(item)} - aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} - /> - ), }, ]; + 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({ @@ -169,8 +251,13 @@ export const CorrelationsTable: React.FC = ({ isExpandable={true} hasActions={true} pagination={true} - search={true} - sorting={true} + search={search} + sorting={{ + sort: { + field: 'timestamp', + direction: 'desc', + }, + }} loading={isLoading} />
diff --git a/public/pages/Findings/components/FindingDetailsFlyout.tsx b/public/pages/Findings/components/FindingDetailsFlyout.tsx index a8c356d1e..9adba27e8 100644 --- a/public/pages/Findings/components/FindingDetailsFlyout.tsx +++ b/public/pages/Findings/components/FindingDetailsFlyout.tsx @@ -30,8 +30,6 @@ import { EuiIcon, EuiTabs, EuiTab, - EuiInMemoryTable, - EuiBasicTableColumn, EuiLoadingContent, } from '@elastic/eui'; import { capitalizeFirstLetter, renderTime } from '../../../utils/helpers'; @@ -46,7 +44,6 @@ import { FindingItemType } from '../containers/Findings/Findings'; import { CorrelationFinding, RuleItemInfoBase } from '../../../../types'; import { FindingFlyoutTabId, FindingFlyoutTabs } from '../utils/constants'; import { DataStore } from '../../../store/DataStore'; -import { ruleTypes } from '../../Rules/utils/constants'; import { CorrelationsTable } from './CorrelationsTable/CorrelationsTable'; export interface FindingDetailsFlyoutBaseProps { @@ -417,12 +414,23 @@ export default class FindingDetailsFlyout extends Component< private getTabContent(tabId: FindingFlyoutTabId, isDocumentLoading = false) { switch (tabId) { case FindingFlyoutTabId.CORRELATIONS: + const logTypes = new Set(); + const ruleSeverity = new Set(); + Object.values(this.state.allRules).forEach((rule) => { + logTypes.add(rule.category); + ruleSeverity.add(rule.level); + }); + return ( ); case FindingFlyoutTabId.DETAILS: @@ -477,7 +485,7 @@ export default class FindingDetailsFlyout extends Component< /> )} {this.createIndexPatternModal()} - + diff --git a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx index 2790af14c..1ed9a091d 100644 --- a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx +++ b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx @@ -13,6 +13,7 @@ import { EuiLink, EuiToolTip, EuiEmptyPrompt, + EuiBadge, } from '@elastic/eui'; import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter'; import dateMath from '@elastic/datemath'; @@ -31,6 +32,7 @@ import { FindingItemType } from '../../containers/Findings/Findings'; import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; import { RuleSource } from '../../../../../server/models/interfaces'; import { DataStore } from '../../../../store/DataStore'; +import { getSeverityColor } from '../../../Correlations/utils/constants'; interface FindingsTableProps extends RouteComponentProps { detectorService: DetectorsService; @@ -193,7 +195,6 @@ export default class FindingsTable extends Component name || DEFAULT_EMPTY_DATA, }, { - // field: 'queries', field: 'logType', name: 'Log type', sortable: true, @@ -205,7 +206,17 @@ export default class FindingsTable extends Component capitalizeFirstLetter(ruleSeverity) || DEFAULT_EMPTY_DATA, + align: 'left', + render: (ruleSeverity: string) => { + const severity = capitalizeFirstLetter(ruleSeverity) || DEFAULT_EMPTY_DATA; + const { background, text } = getSeverityColor(severity); + + return ( + + {severity} + + ); + }, }, { name: 'Actions', @@ -217,7 +228,7 @@ export default class FindingsTable extends Component DataStore.findings.openFlyout(finding, this.state.filteredFindings) } @@ -283,7 +294,7 @@ export default class FindingsTable extends Component { - const severityLevel = ruleSeverity.find((sev) => sev.value === severity); - return {severity || DEFAULT_EMPTY_DATA}; -}; diff --git a/public/store/CorrelationsStore.ts b/public/store/CorrelationsStore.ts index 89fa8795d..5d0ceaba2 100644 --- a/public/store/CorrelationsStore.ts +++ b/public/store/CorrelationsStore.ts @@ -175,7 +175,9 @@ export class CorrelationsStore implements ICorrelationsStore { if (findingRes.ok) { findingRes.response.findings.forEach((f) => { const rule = allRules.find((rule) => rule._id === f.queries[0].id); - + if (!rule) { + console.log(f.queries[0].id); + } findings[f.id] = { ...f, id: f.id, @@ -218,7 +220,7 @@ export class CorrelationsStore implements ICorrelationsStore { const correlatedFindings = response.response.findings.map((f) => { return { ...allFindings[f.finding], - correlationScore: Math.round(100 * f.score) / 100, + correlationScore: f.score.toExponential(2), }; }); return { diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index b325ec055..294a22d9e 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -12,6 +12,7 @@ import { EuiSelectOption, EuiSpacer, EuiText, + EuiBadge, } from '@elastic/eui'; import moment from 'moment'; import { PeriodSchedule } from '../../models/interfaces'; @@ -27,7 +28,7 @@ import { expressionInterpreter as vegaExpressionInterpreter } from 'vega-interpr import { RuleInfo } from '../../server/models/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { OpenSearchService } from '../services'; -import { ruleTypes } from '../pages/Rules/utils/constants'; +import { ruleSeverity, ruleTypes } from '../pages/Rules/utils/constants'; import { Handler } from 'vega-tooltip'; import _ from 'lodash'; @@ -299,3 +300,12 @@ export const formatRuleType = (matchingRuleType: string) => { DEFAULT_EMPTY_DATA ); }; + +export const getSeverityBadge = (severity: string) => { + const severityLevel = ruleSeverity.find((sev) => sev.value === severity); + return ( + + {severity || DEFAULT_EMPTY_DATA} + + ); +}; diff --git a/types/Correlations.ts b/types/Correlations.ts index 4db43c44e..35481ddcc 100644 --- a/types/Correlations.ts +++ b/types/Correlations.ts @@ -25,8 +25,8 @@ export interface CorrelationGraphData { export type CorrelationFinding = { id: string; - correlationScore?: number; - correlationRule?: CorrelationFindingHit; + correlationScore?: string; + correlationRule?: CorrelationRule; logType: string; timestamp: string; detectionRule: { name: string; severity: string };