Skip to content

Commit

Permalink
Fetch all findings and alerts for the detectors when displaying in th…
Browse files Browse the repository at this point in the history
…e tables (#942)

* fetching all findings using the pagination query params; optimized api calls for alert flyout, correlations

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* fetching all alerts; updated correlations UI

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* addressed PR comments; updated snapshots

Signed-off-by: Amardeepsingh Siglani <[email protected]>

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>
  • Loading branch information
amsiglan authored Mar 15, 2024
1 parent 16d11f3 commit 5cd0c8b
Show file tree
Hide file tree
Showing 47 changed files with 521 additions and 404 deletions.
19 changes: 0 additions & 19 deletions models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

export interface Rule {
id: string;
category: string;
log_source: {
product?: string;
category?: string;
service?: string;
};
title: string;
description: string;
tags: Array<{ value: string }>;
false_positives: Array<{ value: string }>;
level: string;
status: string;
references: Array<{ value: string }>;
author: string;
detection: string;
}

export interface PeriodSchedule {
period: {
interval: number;
Expand Down
22 changes: 6 additions & 16 deletions public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
formatRuleType,
renderTime,
} from '../../../../utils/helpers';
import { FindingsService, IndexPatternsService, OpenSearchService } from '../../../../services';
import { IndexPatternsService, OpenSearchService } from '../../../../services';
import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
import { Finding } from '../../../Findings/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
Expand All @@ -38,7 +38,6 @@ import { Detector } from '../../../../../types';
export interface AlertFlyoutProps {
alertItem: AlertItem;
detector: Detector;
findingsService: FindingsService;
notifications: NotificationsStart;
opensearchService: OpenSearchService;
indexPatternService: IndexPatternsService;
Expand Down Expand Up @@ -71,21 +70,12 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt

getFindings = async () => {
this.setState({ loading: true });
const {
alertItem: { detector_id },
findingsService,
notifications,
} = this.props;
const { notifications } = this.props;
try {
const findingRes = await findingsService.getFindings({ detectorId: detector_id });
if (findingRes.ok) {
const relatedFindings = findingRes.response.findings.filter((finding) =>
this.props.alertItem.finding_ids.includes(finding.id)
);
this.setState({ findingItems: relatedFindings });
} else {
errorNotificationToast(notifications, 'retrieve', 'findings', findingRes.error);
}
const relatedFindings = await DataStore.findings.getFindingsByIds(
this.props.alertItem.finding_ids
);
this.setState({ findingItems: relatedFindings });
} catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'findings', e);
}
Expand Down
45 changes: 19 additions & 26 deletions public/pages/Alerts/containers/Alerts/Alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import {
DurationRange,
EuiBasicTableColumn,
EuiButton,
EuiButtonIcon,
Expand All @@ -18,6 +17,7 @@ import {
EuiTitle,
EuiToolTip,
EuiEmptyPrompt,
EuiTableSelectionType,
} from '@elastic/eui';
import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter';
import dateMath from '@elastic/datemath';
Expand Down Expand Up @@ -58,6 +58,8 @@ import { match, RouteComponentProps, withRouter } from 'react-router-dom';
import { DateTimeFilter } from '../../../Overview/models/interfaces';
import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import { Detector } from '../../../../../types';
import { DurationRange } from '@elastic/eui/src/components/date_picker/types';
import { DataStore } from '../../../../store/DataStore';

export interface AlertsProps extends RouteComponentProps {
alertService: AlertsService;
Expand All @@ -66,7 +68,7 @@ export interface AlertsProps extends RouteComponentProps {
opensearchService: OpenSearchService;
notifications: NotificationsStart;
indexPatternService: IndexPatternsService;
match: match;
match: match<{ detectorId: string }>;
dateTimeFilter?: DateTimeFilter;
setDateTimeFilter?: Function;
}
Expand Down Expand Up @@ -192,14 +194,14 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
name: 'Detector',
sortable: true,
dataType: 'string',
render: (detectorName) => detectorName || DEFAULT_EMPTY_DATA,
render: (detectorName: string) => detectorName || DEFAULT_EMPTY_DATA,
},
{
field: 'state',
name: 'Status',
sortable: true,
dataType: 'string',
render: (status) => (status ? capitalizeFirstLetter(status) : DEFAULT_EMPTY_DATA),
render: (status: string) => (status ? capitalizeFirstLetter(status) : DEFAULT_EMPTY_DATA),
},
{
field: 'severity',
Expand Down Expand Up @@ -300,7 +302,7 @@ export class Alerts extends Component<AlertsProps, AlertsState> {

async getAlerts() {
this.setState({ loading: true });
const { alertService, detectorService, notifications } = this.props;
const { detectorService, notifications } = this.props;
const { detectors } = this.state;
try {
const detectorsRes = await detectorService.getDetectors();
Expand All @@ -315,26 +317,19 @@ export class Alerts extends Component<AlertsProps, AlertsState> {

for (let id of detectorIds) {
if (!detectorId || detectorId === id) {
const alertsRes = await alertService.getAlerts({ detector_id: id });

if (alertsRes.ok) {
const detectorAlerts = alertsRes.response.alerts.map((alert) => {
const detector = detectors[id];
if (!alert.detector_id) alert.detector_id = id;
return { ...alert, detectorName: detector.name };
});
alerts = alerts.concat(detectorAlerts);
} else {
errorNotificationToast(notifications, 'retrieve', 'alerts', alertsRes.error);
}
const detectorAlerts = await DataStore.alerts.getAlertsByDetector(
id,
detectors[id].name
);
alerts = alerts.concat(detectorAlerts);
}
}

this.setState({ alerts: alerts, detectors: detectors });
} else {
errorNotificationToast(notifications, 'retrieve', 'detectors', detectorsRes.error);
}
} catch (e) {
} catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'alerts', e);
}
this.filterAlerts();
Expand Down Expand Up @@ -412,7 +407,7 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
}
}
}
} catch (e) {
} catch (e: any) {
errorNotificationToast(notifications, 'acknowledge', 'alerts', e);
}
if (successCount)
Expand All @@ -439,8 +434,8 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
endTime: DEFAULT_DATE_RANGE.end,
},
} = this.props;
const severities = new Set();
const statuses = new Set();
const severities = new Set<string>();
const statuses = new Set<string>();
filteredAlerts.forEach((alert) => {
if (alert) {
severities.add(alert.severity);
Expand Down Expand Up @@ -477,11 +472,10 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
],
};

const selection = {
const selection: EuiTableSelectionType<AlertItem> = {
onSelectionChange: this.onSelectionChange,
selectable: (item) => item.state === ALERT_STATE.ACTIVE,
selectableMessage: (selectable) =>
selectable ? undefined : DISABLE_ACKNOWLEDGED_ALERT_HELP_TEXT,
selectable: (item: AlertItem) => item.state === ALERT_STATE.ACTIVE,
selectableMessage: (selectable) => (selectable ? '' : DISABLE_ACKNOWLEDGED_ALERT_HELP_TEXT),
};

const sorting: any = {
Expand All @@ -500,7 +494,6 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
detector={detectors[flyoutData.alertItem.detector_id]}
onClose={this.onFlyoutClose}
onAcknowledge={this.onAcknowledge}
findingsService={this.props.findingService}
indexPatternService={this.props.indexPatternService}
/>
)}
Expand Down
133 changes: 87 additions & 46 deletions public/pages/Correlations/components/FindingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface FindingCardProps {
id: string;
logType: string;
timestamp: string;
detectionRule: { name: string; severity: string };
detectionRule: CorrelationFinding['detectionRule'];
correlationData?: {
score: string;
onInspect: (findingId: string, logType: string) => void;
Expand Down Expand Up @@ -57,50 +57,56 @@ export const FindingCard: React.FC<FindingCardProps> = ({
});
}

const badgePadding = '0px 4px';
const badgePadding = '0px 6px';
const { text: severityText, background } = getSeverityColor(detectionRule.severity);

const header = (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<div>
<EuiBadge color={background} style={{ padding: badgePadding, color: severityText }}>
{getSeverityLabel(detectionRule.severity)}
</EuiBadge>
<EuiBadge color="hollow" style={{ padding: '0px 4px' }}>
{getLabelFromLogType(logType)}
</EuiBadge>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
<EuiToolTip content={'View finding details'}>
<EuiButtonIcon
aria-label={'View finding details'}
data-test-subj={`view-details-icon`}
iconType={'inspect'}
onClick={() => DataStore.findings.openFlyout(finding, findings, false)}
/>
</EuiToolTip>
<EuiButtonIcon
iconType={correlationData ? 'pin' : 'pinFilled'}
onClick={() => correlationData?.onInspect(id, logType)}
disabled={!correlationData}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
const logTypeAndSeverityItem = (
<div>
<EuiBadge
color={background}
style={{ padding: badgePadding, color: severityText, marginRight: 10 }}
>
{getSeverityLabel(detectionRule.severity)}
</EuiBadge>
<span style={{ fontSize: 12 }}>
<b>{getLabelFromLogType(logType)}</b>
</span>
</div>
);

const attrList = (
<EuiDescriptionList type="column" textStyle="reverse" listItems={list} compressed />
const openFindingFlyoutButton = (
<EuiToolTip content={'View finding details'}>
<EuiButtonIcon
aria-label={'View finding details'}
data-test-subj={`view-details-icon`}
iconType={'inspect'}
onClick={() => DataStore.findings.openFlyout(finding, findings, false)}
/>
</EuiToolTip>
);

const relatedFindingCard = (
<EuiPanel>
{header}
const pinnedFindingHeader = (
<>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiText color="success">{id}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>{openFindingFlyoutButton}</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="xs" alignItems="center">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>{logTypeAndSeverityItem}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{timestamp}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="s" />
</>
);

const relatedFindingHeader = (
<>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiText size="s">
Correlation score{' '}
Expand All @@ -117,24 +123,59 @@ export const FindingCard: React.FC<FindingCardProps> = ({
<b>{correlationData?.score}</b>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
{openFindingFlyoutButton}
<EuiButtonIcon
iconType={'pin'}
onClick={() => correlationData?.onInspect(id, logType)}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>{logTypeAndSeverityItem}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{timestamp}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="s" />
{attrList}
</>
);

const attrList = (
<EuiDescriptionList type="column" textStyle="reverse" listItems={list} compressed />
);

const relatedFindingCard = (
<EuiPanel paddingSize="s">
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiIcon type={'returnKey'} style={{ transform: 'scale(-1, 1)', marginTop: 3 }} />
</EuiFlexItem>
<EuiFlexItem>
{relatedFindingHeader}
{attrList}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);

return correlationData ? (
relatedFindingCard
) : (
const pinnedFindingRuleTags = detectionRule.tags
? detectionRule.tags.map((tag) => <EuiBadge>{tag.value}</EuiBadge>)
: null;

const pinnedFindingCard = (
<EuiPanel>
{header}
<EuiSpacer size="m" />
{pinnedFindingHeader}
{attrList}
<EuiSpacer size="m" />
{pinnedFindingRuleTags}
</EuiPanel>
);

return correlationData ? relatedFindingCard : pinnedFindingCard;
};
Loading

0 comments on commit 5cd0c8b

Please sign in to comment.