Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add correlation rule details into the finding details flyout #563 #565

Merged
7 changes: 6 additions & 1 deletion cypress/support/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,12 @@ Cypress.Commands.add(
cy.get($tr).within(($tr) => {
data.map((rowData) => {
rowData.forEach((tdData) => {
tdData && cy.get($tr).find('td').contains(`${tdData}`);
if (typeof tdData === 'string') {
tdData && cy.get($tr).find('td').contains(`${tdData}`);
} else {
// if rule is an object then use path
tdData && cy.get($tr).find('td').contains(`${tdData.path}`);
}
});
});
});
Expand Down
3 changes: 2 additions & 1 deletion public/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ $euiTextColor: $euiColorDarkestShade !default;
@import "./pages/Overview/components/Widgets/WidgetContainer.scss";
@import "./pages/Main/components/Callout.scss";
@import "./pages/Detectors/components/ReviewFieldMappings/ReviewFieldMappings.scss";
@import "./pages/Correlations//Correlations.scss";
@import "./pages/Correlations/Correlations.scss";
@import "./pages/Findings/components/CorrelationsTable/CorrelationsTable.scss";

.selected-radio-panel {
background-color: tintOrShade($euiColorPrimary, 90%, 70%);
Expand Down
25 changes: 14 additions & 11 deletions public/pages/Correlations/containers/CorrelationsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,17 +391,20 @@ export class Correlations extends React.Component<CorrelationsProps, Correlation
<EuiSpacer />
{findingCardsData.correlatedFindings.map((finding, index) => {
return (
<FindingCard
key={index}
id={finding.id}
logType={finding.logType}
timestamp={finding.timestamp}
detectionRule={finding.detectionRule}
correlationData={{
score: finding.correlationScore || 0,
onInspect: this.onFindingInspect,
}}
/>
<>
<FindingCard
key={index}
id={finding.id}
logType={finding.logType}
timestamp={finding.timestamp}
detectionRule={finding.detectionRule}
correlationData={{
score: finding.correlationScore || 0,
onInspect: this.onFindingInspect,
}}
/>
<EuiSpacer size="m" />
</>
);
})}
</EuiFlyoutBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.correlations-table-details-row {
.correlations-table-details-row-value {
font-weight: 600;
color: $euiColorDarkestShade;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* 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 { getSeverityBadge } from '../../../Rules/utils/helpers';
import {
EuiButton,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiPanel,
EuiInMemoryTable,
EuiBasicTableColumn,
} from '@elastic/eui';
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import { FindingItemType } from '../../containers/Findings/Findings';
import { RouteComponentProps } from 'react-router-dom';

export interface CorrelationsTableProps {
finding: FindingItemType;
correlatedFindings: CorrelationFinding[];
history: RouteComponentProps['history'];
isLoading: boolean;
}

export const CorrelationsTable: React.FC<CorrelationsTableProps> = ({
correlatedFindings,
finding,
history,
isLoading,
}) => {
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{
[key: string]: JSX.Element;
}>({});

const toggleCorrelationDetails = (item: any) => {
const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
if (itemIdToExpandedRowMapValues[item.id]) {
delete itemIdToExpandedRowMapValues[item.id];
} else {
itemIdToExpandedRowMapValues[item.id] = (
<EuiPanel color="subdued" className={'correlations-table-details-row'}>
<EuiFlexGroup justifyContent="flexStart">
<EuiFlexItem grow={false} style={{ minWidth: 200 }}>
<EuiText size={'xs'}>Finding ID</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText size={'xs'} className={'correlations-table-details-row-value'}>
{item.id}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>

<EuiFlexGroup justifyContent="flexStart">
<EuiFlexItem grow={false} style={{ minWidth: 200 }}>
<EuiText size={'xs'}>Threat detector</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText size={'xs'} className={'correlations-table-details-row-value'}>
{item.detectorName}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>

<EuiFlexGroup justifyContent="flexStart">
<EuiFlexItem grow={false} style={{ minWidth: 200 }}>
<EuiText size={'xs'}>Detection rule</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText size={'xs'} className={'correlations-table-details-row-value'}>
{item.detectionRule?.name || '-'}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}

setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
};

const columns: EuiBasicTableColumn<CorrelationFinding>[] = [
{
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',
truncateText: true,
align: 'center',
render: (item: CorrelationFinding) => getSeverityBadge(item.detectionRule.severity),
},
{
field: 'correlationScore',
name: 'Score',
sortable: true,
},
{
align: RIGHT_ALIGNMENT,
width: '40px',
isExpander: true,
render: (item: any) => (
<EuiButtonIcon
onClick={() => toggleCorrelationDetails(item)}
aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'}
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
/>
),
},
];

const goToCorrelationsPage = () => {
history.push({
pathname: `${ROUTES.CORRELATIONS}`,
state: {
finding: finding,
correlatedFindings: correlatedFindings,
},
});
};

return (
<>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s">
<h3>Correlated findings</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => goToCorrelationsPage()}
disabled={correlatedFindings.length === 0}
>
View correlations graph
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiInMemoryTable
columns={columns}
items={correlatedFindings}
itemId="id"
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
isExpandable={true}
hasActions={true}
pagination={true}
search={true}
sorting={true}
loading={isLoading}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};
Loading
Loading