Skip to content

Commit

Permalink
Updated get findings & alerts to use duration filter and start showin…
Browse files Browse the repository at this point in the history
…g results as they come in (opensearch-project#1031)

* updated get findings & alerts to use duration filter and stream results

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

* refactored code to use AbortController

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

* minor UI tweaks

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

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>
  • Loading branch information
amsiglan authored May 22, 2024
1 parent 283fdd9 commit b8cb0ae
Show file tree
Hide file tree
Showing 27 changed files with 505 additions and 176 deletions.
9 changes: 4 additions & 5 deletions public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { AlertItem, RuleSource } from '../../../../../server/models/interfaces';
import { RuleSource } from '../../../../../server/models/interfaces';
import React from 'react';
import { ContentPanel } from '../../../../components/ContentPanel';
import { ALERT_STATE, DEFAULT_EMPTY_DATA, ROUTES } from '../../../../utils/constants';
Expand All @@ -30,10 +30,9 @@ import {
} from '../../../../utils/helpers';
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';
import { DataStore } from '../../../../store/DataStore';
import { Detector } from '../../../../../types';
import { AlertItem, Detector, Finding } from '../../../../../types';

export interface AlertFlyoutProps {
alertItem: AlertItem;
Expand Down Expand Up @@ -135,7 +134,7 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt
name: 'Finding ID',
sortable: true,
dataType: 'string',
render: (id, finding: any) =>
render: (id: string, finding: any) =>
(
<EuiLink
onClick={() => {
Expand All @@ -159,7 +158,7 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt
}}
data-test-subj={'finding-details-flyout-button'}
>
{`${(id as string).slice(0, 7)}...`}
{id.length > 7 ? `${id.slice(0, 7)}...` : id}
</EuiLink>
) || DEFAULT_EMPTY_DATA,
},
Expand Down
39 changes: 28 additions & 11 deletions public/pages/Alerts/containers/Alerts/Alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
capitalizeFirstLetter,
createSelectComponent,
errorNotificationToast,
getDuration,
renderTime,
renderVisualization,
successNotificationToast,
Expand All @@ -67,7 +68,7 @@ export interface AlertsProps extends RouteComponentProps, DataSourceProps {
notifications: NotificationsStart;
indexPatternService: IndexPatternsService;
match: match<{ detectorId: string }>;
dateTimeFilter?: DateTimeFilter;
dateTimeFilter: DateTimeFilter;
setDateTimeFilter?: Function;
}

Expand All @@ -93,6 +94,7 @@ const groupByOptions = [

export class Alerts extends Component<AlertsProps, AlertsState> {
static contextType = CoreServicesContext;
private abortControllers: AbortController[] = [];

constructor(props: AlertsProps) {
super(props);
Expand Down Expand Up @@ -300,32 +302,39 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
this.onRefresh();
}

async getAlerts() {
this.setState({ loading: true });
const { detectorService, notifications } = this.props;
componentWillUnmount(): void {
this.abortPendingGetAlerts();
}

async getAlerts(abort: AbortSignal) {
this.setState({ loading: true, alerts: [] });
const { detectorService, notifications, dateTimeFilter } = this.props;
const { detectors } = this.state;
try {
const detectorsRes = await detectorService.getDetectors();
const duration = getDuration(dateTimeFilter);
if (detectorsRes.ok) {
this.setState({ detectors: detectors });
const detectorIds = detectorsRes.response.hits.hits.map((hit) => {
detectors[hit._id] = { ...hit._source, id: hit._id };
return hit._id;
});

let alerts: AlertItem[] = [];
const detectorId = this.props.match.params['detectorId'];

for (let id of detectorIds) {
if (!detectorId || detectorId === id) {
const detectorAlerts = await DataStore.alerts.getAlertsByDetector(
await DataStore.alerts.getAlertsByDetector(
id,
detectors[id].name
detectors[id].name,
abort,
duration,
(alerts) => {
this.setState({ alerts: [...this.state.alerts, ...alerts]})
}
);
alerts = alerts.concat(detectorAlerts);
}
}

this.setState({ alerts: alerts, detectors: detectors });
} else {
errorNotificationToast(notifications, 'retrieve', 'detectors', detectorsRes.error);
}
Expand Down Expand Up @@ -372,8 +381,16 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
});
};

private abortPendingGetAlerts() {
this.abortControllers.forEach(controller => controller.abort());
this.abortControllers = [];
}

onRefresh = async () => {
this.getAlerts();
this.abortPendingGetAlerts();
const abortController = new AbortController();
this.abortControllers.push(abortController);
this.getAlerts(abortController.signal);
renderVisualization(this.generateVisualizationSpec(this.state.filteredAlerts), 'alerts-view');
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,24 @@ exports[`<Alerts /> spec renders the component 1`] = `
itemId={[Function]}
items={Array []}
loading={true}
message={
<EuiEmptyPrompt
body={
<p>
<span
style={
Object {
"display": "block",
}
}
>
No alerts.
</span>
Adjust the time range to see more results.
</p>
}
/>
}
pagination={true}
responsive={true}
search={
Expand Down Expand Up @@ -1745,7 +1763,24 @@ exports[`<Alerts /> spec renders the component 1`] = `
itemId={[Function]}
items={Array []}
loading={true}
noItemsMessage="No items found"
noItemsMessage={
<EuiEmptyPrompt
body={
<p>
<span
style={
Object {
"display": "block",
}
}
>
No alerts.
</span>
Adjust the time range to see more results.
</p>
}
/>
}
onChange={[Function]}
pagination={
Object {
Expand Down Expand Up @@ -2408,7 +2443,53 @@ exports[`<Alerts /> spec renders the component 1`] = `
<span
className="euiTableCellContent__text"
>
No items found
<EuiEmptyPrompt
body={
<p>
<span
style={
Object {
"display": "block",
}
}
>
No alerts.
</span>
Adjust the time range to see more results.
</p>
}
>
<div
className="euiEmptyPrompt"
>
<EuiTextColor
color="subdued"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<EuiText>
<div
className="euiText euiText--medium"
>
<p>
<span
style={
Object {
"display": "block",
}
}
>
No alerts.
</span>
Adjust the time range to see more results.
</p>
</div>
</EuiText>
</span>
</EuiTextColor>
</div>
</EuiEmptyPrompt>
</span>
</div>
</td>
Expand Down
14 changes: 11 additions & 3 deletions public/pages/Correlations/containers/CorrelationsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CorrelationGraphData,
DataSourceProps,
DateTimeFilter,
FindingItemType,
} from '../../../../types';
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
Expand Down Expand Up @@ -50,12 +51,13 @@ import {
import { CorrelationGraph } from '../components/CorrelationGraph';
import { FindingCard } from '../components/FindingCard';
import { DataStore } from '../../../store/DataStore';
import { FindingItemType } from '../../Findings/containers/Findings/Findings';
import datemath from '@elastic/datemath';
import { ruleSeverity } from '../../Rules/utils/constants';
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 } from '../../../utils/helpers';

interface CorrelationsProps
extends RouteComponentProps<
Expand All @@ -67,6 +69,7 @@ interface CorrelationsProps
setDateTimeFilter?: Function;
dateTimeFilter?: DateTimeFilter;
onMount: () => void;
notifications: NotificationsStart | null;
}

interface SpecificFindingCorrelations {
Expand Down Expand Up @@ -237,8 +240,13 @@ export class Correlations extends React.Component<CorrelationsProps, Correlation
if (node) {
detectorType = node.saLogType;
} else {
const allFindings = await DataStore.correlations.fetchAllFindings();
detectorType = allFindings[findingId].logType;
const finding = (await DataStore.findings.getFindingsByIds([findingId]))[0];
detectorType = finding?.detectionType;
}

if (!detectorType) {
errorNotificationToast(this.props.notifications, 'show', 'correlated findings');
return;
}

const correlatedFindingsInfo = await DataStore.correlations.getCorrelatedFindings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import React, { useState } from 'react';
import { CorrelationFinding } from '../../../../../types';
import { CorrelationFinding, FindingItemType } from '../../../../../types';
import { ruleTypes } from '../../../Rules/utils/constants';
import { DEFAULT_EMPTY_DATA, ROUTES } from '../../../../utils/constants';
import {
Expand All @@ -20,7 +20,6 @@ import {
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';
Expand Down
16 changes: 13 additions & 3 deletions public/pages/Findings/components/FindingDetailsFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ import { RuleSource } from '../../../../server/models/interfaces';
import { OpenSearchService, IndexPatternsService, CorrelationService } from '../../../services';
import { RuleTableItem } from '../../Rules/utils/helpers';
import { CreateIndexPatternForm } from './CreateIndexPatternForm';
import { FindingItemType } from '../containers/Findings/Findings';
import { CorrelationFinding, FindingDocumentItem, RuleItemInfoBase } from '../../../../types';
import { CorrelationFinding, FindingDocumentItem, RuleItemInfoBase, FindingItemType } from '../../../../types';
import { FindingFlyoutTabId, FindingFlyoutTabs } from '../utils/constants';
import { DataStore } from '../../../store/DataStore';
import { CorrelationsTable } from './CorrelationsTable/CorrelationsTable';
Expand Down Expand Up @@ -88,6 +87,8 @@ export default class FindingDetailsFlyout extends Component<
FindingDetailsFlyoutProps,
FindingDetailsFlyoutState
> {
private abortGetFindingsControllers: AbortController[] = [];

constructor(props: FindingDetailsFlyoutProps) {
super(props);
const relatedDocuments: FindingDocumentItem[] = this.getRelatedDocuments();
Expand Down Expand Up @@ -121,12 +122,21 @@ export default class FindingDetailsFlyout extends Component<
};
}

componentWillUnmount(): void {
this.abortGetFindingsControllers.forEach(controller => {
controller.abort();
})
this.abortGetFindingsControllers = [];
}

getCorrelations = async () => {
const { id, detector } = this.props.finding;
let allFindings = this.props.findings;
if (this.props.shouldLoadAllFindings) {
// if findings come from the alerts fly-out, we need to get all the findings to match those with the correlations
allFindings = await DataStore.findings.getAllFindings();
const abortController = new AbortController();
this.abortGetFindingsControllers.push(abortController);
allFindings = await DataStore.findings.getAllFindings(abortController.signal);
}

DataStore.correlations.getCorrelationRules().then((correlationRules) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ import {
IndexPatternsService,
CorrelationService,
} from '../../../../services';
import { Finding } from '../../models/interfaces';
import CreateAlertFlyout from '../CreateAlertFlyout';
import { NotificationChannelTypeOptions } from '../../../CreateDetector/components/ConfigureAlerts/models/interfaces';
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';
import { Finding, FindingItemType } from '../../../../../types';

interface FindingsTableProps extends RouteComponentProps {
detectorService: DetectorsService;
Expand Down Expand Up @@ -177,13 +176,13 @@ export default class FindingsTable extends Component<FindingsTableProps, Finding
name: 'Finding ID',
sortable: true,
dataType: 'string',
render: (id, finding) =>
render: (id: string, finding) =>
(
<EuiLink
onClick={() => DataStore.findings.openFlyout(finding, this.state.filteredFindings)}
data-test-subj={'finding-details-flyout-button'}
>
{`${(id as string).slice(0, 7)}...`}
{id.length > 7 ? `${id.slice(0, 7)}...` : id}
</EuiLink>
) || DEFAULT_EMPTY_DATA,
},
Expand Down
Loading

0 comments on commit b8cb0ae

Please sign in to comment.