diff --git a/public/pages/Alerts/components/CorrelationAlertFlyout/CorrelationAlertFlyout.tsx b/public/pages/Alerts/components/CorrelationAlertFlyout/CorrelationAlertFlyout.tsx index 7818c3cd..dfb659fc 100644 --- a/public/pages/Alerts/components/CorrelationAlertFlyout/CorrelationAlertFlyout.tsx +++ b/public/pages/Alerts/components/CorrelationAlertFlyout/CorrelationAlertFlyout.tsx @@ -32,7 +32,7 @@ import { import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { DataStore } from '../../../../store/DataStore'; - import { CorrelationAlertItem, Detector, Finding } from '../../../../../types'; + import { CorrelationAlertItem, Finding } from '../../../../../types'; export interface CorrelationAlertFlyoutProps { alertItem: CorrelationAlertItem; @@ -147,7 +147,7 @@ import { onClick={() => { const ruleId = finding.queries[0]?.id; // Assuming you retrieve rule ID from finding const rule: RuleSource | undefined = rules[ruleId]; - + DataStore.findings.openFlyout( { ...finding, @@ -177,10 +177,7 @@ import { sortable: true, dataType: 'string', render: (finding: any) => { - const ruleId = finding.queries[0]?.id; // Retrieve rule ID from the first query of the finding - const rule: RuleSource | undefined = rules[ruleId]; - - return formatRuleType(rule?.category || ''); // Pass category from rule as string, default to empty string if rule is undefined + return formatRuleType(finding[0]?.tags[1]); // Pass category from rule as string, default to empty string if rule is undefined }, }, ]; diff --git a/public/pages/Alerts/containers/Alerts/Alerts.tsx b/public/pages/Alerts/containers/Alerts/Alerts.tsx index 47f7f4e5..d70fecc1 100644 --- a/public/pages/Alerts/containers/Alerts/Alerts.tsx +++ b/public/pages/Alerts/containers/Alerts/Alerts.tsx @@ -93,6 +93,7 @@ export interface AlertsState { timeUnit: TimeUnit; dateFormat: string; widgetEmptyMessage: React.ReactNode | undefined; + widgetEmptyCorrelationMessage: React.ReactNode | undefined; tab: string; } @@ -131,6 +132,7 @@ export class Alerts extends Component { timeUnit: timeUnits.timeUnit, dateFormat: timeUnits.dateFormat, widgetEmptyMessage: undefined, + widgetEmptyCorrelationMessage: undefined, tab: 'findings' }; } @@ -154,12 +156,8 @@ export class Alerts extends Component { prevState.correlationAlerts !== this.state.correlationAlerts || prevState.correlationAlerts.length !== this.state.correlationAlerts.length; - if (prevState.tab != this.state.tab) { - if (this.state.tab == "findings") { - renderVisualization(this.generateVisualizationSpec(this.state.filteredAlerts), 'alerts-view'); - } else { - renderVisualization(this.generateCorrelationVisualizationSpec(this.state.filteredCorrelationAlerts), 'alerts-view'); - } + if (prevState.tab !== this.state.tab) { + this.onRefresh(); } if (this.props.dataSource !== prevProps.dataSource) { this.onRefresh(); @@ -167,9 +165,8 @@ export class Alerts extends Component { this.filterAlerts(); } else if (correlationAlertsChanged) { this.filterCorrelationAlerts(); - } - else if (this.state.groupBy !== prevState.groupBy) { - renderVisualization(this.generateVisualizationSpec(this.state.filteredAlerts), 'alerts-view'); + } else if (this.state.groupBy !== prevState.groupBy) { + this.renderVisAsPerTab(); } } @@ -219,7 +216,7 @@ export class Alerts extends Component { this.setState({ alertsFiltered: true, filteredCorrelationAlerts: filteredCorrelationAlerts, - widgetEmptyMessage: filteredCorrelationAlerts.length ? undefined : ( + widgetEmptyCorrelationMessage: filteredCorrelationAlerts.length ? undefined : ( @@ -233,6 +230,25 @@ export class Alerts extends Component { renderVisualization(this.generateCorrelationVisualizationSpec(filteredCorrelationAlerts), 'alerts-view'); }; + private renderVisAsPerTab() { + if (this.state.tab === "findings") { + renderVisualization(this.generateVisualizationSpec(this.state.filteredAlerts), 'alerts-view'); + } else { + renderVisualization(this.generateCorrelationVisualizationSpec(this.state.filteredCorrelationAlerts), 'alerts-view'); + } + } + + private getAlertsAsPerTab() { + if (this.state.tab === "findings") { + this.abortPendingGetAlerts(); + const abortController = new AbortController(); + this.abortControllers.push(abortController); + this.getAlerts(abortController.signal); + } else { + this.getCorrelationAlerts(); + } + } + getColumns(): EuiBasicTableColumn[] { return [ { @@ -481,10 +497,9 @@ export class Alerts extends Component { async getCorrelationAlerts() { this.setState({ loading: true, correlationAlerts: [] }); - const { correlationService, notifications, dateTimeFilter } = this.props; + const { correlationService, notifications } = this.props; try { const correlationRes = await correlationService.getCorrelationAlerts(); - const duration = getDuration(dateTimeFilter); if (correlationRes.ok) { this.setState({ correlationAlerts: correlationRes.response.correlationAlerts }); } else { @@ -549,6 +564,19 @@ export class Alerts extends Component { ); } + createAcknowledgeControlForCorrelations() { + const { correlatedItems } = this.state; + return ( + this.onAcknowledgeCorrelationAlert(correlatedItems)} + data-test-subj={'acknowledge-button'} + > + Acknowledge + + ); + } + onTimeChange = ({ start, end }: { start: string; end: string }) => { let { recentlyUsedRanges } = this.state; recentlyUsedRanges = recentlyUsedRanges.filter( @@ -578,13 +606,8 @@ export class Alerts extends Component { } onRefresh = async () => { - this.abortPendingGetAlerts(); - const abortController = new AbortController(); - this.abortControllers.push(abortController); - this.getAlerts(abortController.signal); - this.getCorrelationAlerts(); - renderVisualization(this.generateVisualizationSpec(this.state.filteredAlerts), 'alerts-view'); - renderVisualization(this.generateCorrelationVisualizationSpec(this.state.filteredCorrelationAlerts), 'alerts-view'); + this.getAlertsAsPerTab(); + this.renderVisAsPerTab(); }; onSelectionChange = (selectedItems: AlertItem[]) => { @@ -638,21 +661,17 @@ export class Alerts extends Component { let successCount = 0; try { // Separating the selected items by detector ID, and adding all selected alert IDs to an array for that detector ID. - const correlations: { [key: string]: string[] } = {}; + const alertIds: string[] = []; selectedItems.forEach((item) => { - if (!correlations[item.correlation_rule_id]) correlations[item.correlation_rule_id] = [item.id]; - else correlations[item.correlation_rule_id].push(item.id); + alertIds.push(item.id); }); - for (let corrId of Object.keys(correlations)) { - const alertIds = correlations[corrId]; - if (alertIds.length > 0) { - const response = await correlationService.acknowledgeCorrelationAlerts(alertIds); - if (response.ok) { - successCount += alertIds.length; - } else { - errorNotificationToast(notifications, 'acknowledge', 'alerts', response.error); - } + if (alertIds.length > 0) { + const response = await correlationService.acknowledgeCorrelationAlerts(alertIds); + if (response.ok) { + successCount += alertIds.length; + } else { + errorNotificationToast(notifications, 'acknowledge', 'alerts', response.error); } } } catch (e: any) { @@ -677,6 +696,7 @@ export class Alerts extends Component { loading, recentlyUsedRanges, widgetEmptyMessage, + widgetEmptyCorrelationMessage, } = this.state; const { @@ -851,7 +871,11 @@ export class Alerts extends Component { - + this.setState({ tab: 'findings' })} isSelected={this.state.tab === 'findings'}> Findings @@ -886,7 +910,7 @@ export class Alerts extends Component { sorting={sorting} selection={correlationSelection} loading={loading} - message={widgetEmptyMessage} + message={widgetEmptyCorrelationMessage} /> )} diff --git a/public/pages/Correlations/containers/CreateCorrelationRule.tsx b/public/pages/Correlations/containers/CreateCorrelationRule.tsx index 225c0e1c..b0ed6f6a 100644 --- a/public/pages/Correlations/containers/CreateCorrelationRule.tsx +++ b/public/pages/Correlations/containers/CreateCorrelationRule.tsx @@ -254,6 +254,21 @@ export const CreateCorrelationRule: React.FC = ( }, [props.dataSource]); useEffect(() => { + const alertCondition = initialValues.trigger; + if (alertCondition && alertCondition.actions) { + if (alertCondition.actions.length == 0) + alertCondition.actions = getEmptyAlertCondition().actions; + + const channelId = alertCondition?.actions[0].destination_id; + const selectedNotificationChannelOption: NotificationChannelOption[] = []; + if (channelId) { + notificationChannels.forEach((typeOption) => { + const matchingChannel = typeOption.options.find((option) => option.value === channelId); + if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel); + }); + } + setSelectedNotificationChannelOption(selectedNotificationChannelOption); + } setPeriod(parseTime(initialValues.time_window)); setGroupByEnabled(initialValues.queries.some((q) => !!q.field)); setDataFilterEnabled(initialValues.queries.some((q) => q.conditions.length > 0)); @@ -261,7 +276,7 @@ export const CreateCorrelationRule: React.FC = ( initialValues.queries.forEach(({ index }) => { updateLogFieldsForIndex(index); }); - }, [initialValues]); + }, [initialValues, notificationChannels]); const onNotificationChannelsChange = (selectedOptions: EuiComboBoxOptionOption[]) => { @@ -823,24 +838,6 @@ export const CreateCorrelationRule: React.FC = ( ); }; - useEffect(() => { - const alertCondition = initialValues.trigger; - if (alertCondition && alertCondition.actions) { - if (alertCondition.actions.length == 0) - alertCondition.actions = getEmptyAlertCondition().actions; - - const channelId = alertCondition?.actions[0].destination_id; - const selectedNotificationChannelOption: NotificationChannelOption[] = []; - if (channelId) { - notificationChannels.forEach((typeOption) => { - const matchingChannel = typeOption.options.find((option) => option.value === channelId); - if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel); - }); - } - setSelectedNotificationChannelOption(selectedNotificationChannelOption); - } - }, [initialValues, notificationChannels]); - useEffect(() => { context?.chrome.setBreadcrumbs([ BREADCRUMBS.SECURITY_ANALYTICS, @@ -1041,7 +1038,7 @@ export const CreateCorrelationRule: React.FC = ( value={trigger?.name} onChange={(e) => { const triggerName = e.target.value || ''; - props.handleChange('trigger?.name')(triggerName); + props.setFieldValue('trigger?.name', triggerName) onNameChange(triggerName); }} data-test-subj="alert-condition-name" diff --git a/public/services/CorrelationService.ts b/public/services/CorrelationService.ts index c4b9a127..e0664b3a 100644 --- a/public/services/CorrelationService.ts +++ b/public/services/CorrelationService.ts @@ -28,7 +28,7 @@ export default class CorrelationService implements ICorrelationsService { const url = `..${API.ACK_CORRELATION_ALERTS}`; return (await this.httpClient.post(url, { - body: JSON.stringify(body), + body: JSON.stringify({alertIds: body}), query: { dataSourceId: dataSourceInfo.activeDataSource.id, }, diff --git a/server/routes/FindingsRoutes.ts b/server/routes/FindingsRoutes.ts index 6a9b6875..d5ddf1f8 100644 --- a/server/routes/FindingsRoutes.ts +++ b/server/routes/FindingsRoutes.ts @@ -25,7 +25,7 @@ export function setupFindingsRoutes(services: NodeServices, router: IRouter) { detectionType: schema.maybe(schema.string()), severity: schema.maybe(schema.string()), searchString: schema.maybe(schema.string()), - findingIds: schema.string(), + findingIds: schema.maybe(schema.string()), startTime: schema.maybe(schema.number()), endTime: schema.maybe(schema.number()) }),