Skip to content

Commit

Permalink
[RAM][O11Y] Integrate Conditional Actions with several Observability …
Browse files Browse the repository at this point in the history
…rule types (elastic#159522)

## Summary

Closes elastic#159520

<img width="573" alt="Screenshot 2023-06-12 at 3 12 27 PM"
src="https://github.com/elastic/kibana/assets/1445834/ec16b8d7-25a5-435c-bf29-7392747b8c0f">

### Checklist

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Patryk Kopyciński <contact@patrykkopycinski.com>
(cherry picked from commit faadf34)
  • Loading branch information
Zacqary committed Jun 27, 2023
1 parent f3440a9 commit 0af4823
Show file tree
Hide file tree
Showing 25 changed files with 111 additions and 11 deletions.
Expand Up @@ -72,6 +72,7 @@ type AuthorizedConsumers = Record<string, HasPrivileges>;
export interface RegistryAlertTypeWithAuth extends RegistryRuleType {
authorizedConsumers: AuthorizedConsumers;
hasGetSummarizedAlerts?: boolean;
hasFieldsForAAD?: boolean;
}

type IsAuthorizedAtProducerLevel = boolean;
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/alerting/server/routes/rule_types.test.ts
Expand Up @@ -88,6 +88,7 @@ describe('ruleTypesRoute', () => {
producer: 'test',
enabled_in_license: true,
has_get_summarized_alerts: true,
has_fields_for_a_a_d: false,
},
];
rulesClient.listRuleTypes.mockResolvedValueOnce(new Set(listTypes));
Expand All @@ -113,6 +114,7 @@ describe('ruleTypesRoute', () => {
"default_schedule_interval": "10m",
"does_set_recovery_context": false,
"enabled_in_license": true,
"has_fields_for_a_a_d": false,
"has_get_summarized_alerts": true,
"id": "1",
"is_exportable": true,
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/alerting/server/routes/rule_types.ts
Expand Up @@ -26,6 +26,7 @@ const rewriteBodyRes: RewriteResponseCase<RegistryAlertTypeWithAuth[]> = (result
defaultScheduleInterval,
doesSetRecoveryContext,
hasGetSummarizedAlerts,
hasFieldsForAAD,
...rest
}) => ({
...rest,
Expand All @@ -41,6 +42,7 @@ const rewriteBodyRes: RewriteResponseCase<RegistryAlertTypeWithAuth[]> = (result
default_schedule_interval: defaultScheduleInterval,
does_set_recovery_context: doesSetRecoveryContext,
has_get_summarized_alerts: !!hasGetSummarizedAlerts,
has_fields_for_a_a_d: !!hasFieldsForAAD,
})
);
};
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.test.ts
Expand Up @@ -704,6 +704,7 @@ describe('Create Lifecycle', () => {
"defaultScheduleInterval": undefined,
"doesSetRecoveryContext": false,
"enabledInLicense": false,
"hasFieldsForAAD": false,
"hasGetSummarizedAlerts": false,
"id": "test",
"isExportable": true,
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.ts
Expand Up @@ -61,6 +61,7 @@ export interface RegistryRuleType
| 'ruleTaskTimeout'
| 'defaultScheduleInterval'
| 'doesSetRecoveryContext'
| 'fieldsForAAD'
> {
id: string;
enabledInLicense: boolean;
Expand Down Expand Up @@ -372,6 +373,7 @@ export class RuleTypeRegistry {
doesSetRecoveryContext,
alerts,
getSummarizedAlerts,
fieldsForAAD,
},
]: [string, UntypedNormalizedRuleType]) => ({
id,
Expand All @@ -392,6 +394,7 @@ export class RuleTypeRegistry {
minimumLicenseRequired
).isValid,
hasGetSummarizedAlerts: !!getSummarizedAlerts,
hasFieldsForAAD: Boolean(fieldsForAAD),
...(alerts ? { alerts } : {}),
})
)
Expand Down
Expand Up @@ -49,6 +49,7 @@ import {
WARNING_ACTIONS,
} from './inventory_metric_threshold_executor';
import { MetricsRulesTypeAlertDefinition } from '../register_rule_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';

const condition = schema.object({
threshold: schema.arrayOf(schema.number()),
Expand Down Expand Up @@ -146,5 +147,6 @@ export async function registerMetricInventoryThresholdRuleType(
},
getSummarizedAlerts: libs.metricsRules.createGetSummarizedAlerts(),
alerts: MetricsRulesTypeAlertDefinition,
fieldsForAAD: O11Y_AAD_FIELDS,
});
}
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import { PluginSetupContract } from '@kbn/alerting-plugin/server';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import { extractReferences, injectReferences } from './log_threshold_references_manager';
import {
Expand Down Expand Up @@ -165,5 +166,6 @@ export async function registerLogThresholdRuleType(
injectReferences,
},
alerts: LogsRulesTypeAlertDefinition,
fieldsForAAD: O11Y_AAD_FIELDS,
});
}
Expand Up @@ -14,6 +14,7 @@ import {
AlertInstanceContext as AlertContext,
} from '@kbn/alerting-plugin/server';
import { RecoveredActionGroupId } from '@kbn/alerting-plugin/common';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
import {
createMetricAnomalyExecutor,
FIRED_ACTIONS,
Expand Down Expand Up @@ -67,6 +68,7 @@ export const registerMetricAnomalyRuleType = (
minimumLicenseRequired: 'basic',
isExportable: true,
executor: createMetricAnomalyExecutor(libs, ml),
fieldsForAAD: O11Y_AAD_FIELDS,
actionVariables: {
context: [
{ name: 'alertState', description: alertStateActionVariableDescription },
Expand Down
Expand Up @@ -252,6 +252,7 @@ describe('alert_form', () => {
}
actionTypeRegistry={actionTypeRegistry}
featureId="alerting"
producerId="alerting"
/>
</KibanaReactContext.Provider>
</I18nProvider>
Expand Down
Expand Up @@ -23,6 +23,7 @@ import type {
} from '@kbn/alerting-plugin/common';
import { SecurityConnectorFeatureId } from '@kbn/actions-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { NOTIFICATION_DEFAULT_FREQUENCY } from '../../../../../common/constants';
import type { FieldHook } from '../../../../shared_imports';
import { useFormContext } from '../../../../shared_imports';
Expand Down Expand Up @@ -243,13 +244,13 @@ export const RuleActionsField: React.FC<Props> = ({
setActionFrequencyProperty: setActionFrequency,
setActionAlertsFilterProperty,
featureId: SecurityConnectorFeatureId,
producerId: AlertConsumers.SIEM,
defaultActionMessage: FORM_FOR_EACH_ALERT_BODY_MESSAGE,
defaultSummaryMessage: FORM_SUMMARY_BODY_MESSAGE,
hideActionHeader: true,
hasSummary: true,
notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS,
defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY,
showActionAlertsFilter: true,
}),
[
actions,
Expand Down
Expand Up @@ -100,6 +100,7 @@ const baseProps = {
recoveryActionGroup: 'recovered',
actionTypeRegistry,
minimumThrottleInterval: [1, 'm'] as [number | undefined, string],
producerId: 'infratstructure',
setActions: jest.fn(),
setActionIdByIndex: jest.fn(),
setActionParamsProperty: jest.fn(),
Expand Down
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { DataViewField } from '@kbn/data-views-plugin/common';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common';
import useAsync from 'react-use/lib/useAsync';
import type { AsyncState } from 'react-use/lib/useAsync';
import { TriggersAndActionsUiServices } from '../..';

export function useRuleAADFields(ruleTypeId?: string): AsyncState<DataViewField[]> {
const { http } = useKibana<TriggersAndActionsUiServices>().services;

const aadFields = useAsync(async () => {
if (!ruleTypeId) return [];
const fields = await http.get<DataViewField[]>(`${BASE_RAC_ALERTS_API_PATH}/aad_fields`, {
query: { ruleTypeId },
});

return fields;
});

return aadFields;
}
Expand Up @@ -25,6 +25,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
does_set_recovery_context: doesSetRecoveryContext,
default_schedule_interval: defaultScheduleInterval,
has_get_summarized_alerts: hasGetSummarizedAlerts,
has_fields_for_a_a_d: hasFieldsForAAD,
...rest
}: AsApiContract<RuleType>) => ({
enabledInLicense,
Expand All @@ -38,6 +39,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
doesSetRecoveryContext,
defaultScheduleInterval,
hasGetSummarizedAlerts,
hasFieldsForAAD,
...rest,
});

Expand Down
Expand Up @@ -6,6 +6,7 @@
*/

import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { ValidFeatureId } from '@kbn/rule-data-utils';
import { Filter } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { EuiSwitch, EuiSpacer } from '@elastic/eui';
Expand All @@ -16,11 +17,17 @@ import { AlertsSearchBar } from '../alerts_search_bar';
interface ActionAlertsFilterQueryProps {
state?: AlertsFilter['query'];
onChange: (update?: AlertsFilter['query']) => void;
appName: string;
featureIds: ValidFeatureId[];
ruleTypeId?: string;
}

export const ActionAlertsFilterQuery: React.FC<ActionAlertsFilterQueryProps> = ({
state,
onChange,
appName,
featureIds,
ruleTypeId,
}) => {
const [query, setQuery] = useState(state ?? { kql: '', filters: [] });

Expand Down Expand Up @@ -61,7 +68,7 @@ export const ActionAlertsFilterQuery: React.FC<ActionAlertsFilterQueryProps> = (
label={i18n.translate(
'xpack.triggersActionsUI.sections.actionTypeForm.ActionAlertsFilterQueryToggleLabel',
{
defaultMessage: 'if alert matches a query',
defaultMessage: 'If alert matches a query',
}
)}
checked={queryEnabled}
Expand All @@ -72,9 +79,10 @@ export const ActionAlertsFilterQuery: React.FC<ActionAlertsFilterQueryProps> = (
<>
<EuiSpacer size="s" />
<AlertsSearchBar
appName="siem"
appName={appName}
featureIds={featureIds}
ruleTypeId={ruleTypeId}
disableQueryLanguageSwitcher={true}
featureIds={['siem']}
query={query.kql}
filters={query.filters ?? []}
onQueryChange={onQueryChange}
Expand Down
Expand Up @@ -146,7 +146,7 @@ export const ActionAlertsFilterTimeframe: React.FC<ActionAlertsFilterTimeframePr
label={i18n.translate(
'xpack.triggersActionsUI.sections.actionTypeForm.ActionAlertsFilterTimeframeToggleLabel',
{
defaultMessage: 'if alert is generated during timeframe',
defaultMessage: 'If alert is generated during timeframe',
}
)}
checked={timeframeEnabled}
Expand Down
Expand Up @@ -314,6 +314,7 @@ describe('action_form', () => {
context: [{ name: 'contextVar', description: 'context var1' }],
}}
featureId="alerting"
producerId="alerting"
defaultActionGroupId={'default'}
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) => {
const recoveryActionGroupId = customRecoveredActionGroup
Expand Down
Expand Up @@ -69,6 +69,7 @@ export interface ActionAccordionFormProps {
index: number
) => void;
featureId: string;
producerId: string;
messageVariables?: ActionVariables;
summaryMessageVariables?: ActionVariables;
setHasActionsDisabled?: (value: boolean) => void;
Expand All @@ -83,7 +84,8 @@ export interface ActionAccordionFormProps {
minimumThrottleInterval?: [number | undefined, string];
notifyWhenSelectOptions?: NotifyWhenSelectOptions[];
defaultRuleFrequency?: RuleActionFrequency;
showActionAlertsFilter?: boolean;
ruleTypeId?: string;
hasFieldsForAAD?: boolean;
}

interface ActiveActionConnectorState {
Expand Down Expand Up @@ -117,7 +119,9 @@ export const ActionForm = ({
minimumThrottleInterval,
notifyWhenSelectOptions,
defaultRuleFrequency = DEFAULT_FREQUENCY,
showActionAlertsFilter,
ruleTypeId,
producerId,
hasFieldsForAAD,
}: ActionAccordionFormProps) => {
const {
http,
Expand Down Expand Up @@ -491,7 +495,10 @@ export const ActionForm = ({
minimumThrottleInterval={minimumThrottleInterval}
notifyWhenSelectOptions={notifyWhenSelectOptions}
defaultNotifyWhenValue={defaultRuleFrequency.notifyWhen}
showActionAlertsFilter={showActionAlertsFilter}
featureId={featureId}
producerId={producerId}
ruleTypeId={ruleTypeId}
hasFieldsForAAD={hasFieldsForAAD}
/>
);
})}
Expand Down
Expand Up @@ -634,6 +634,8 @@ function getActionTypeForm({
summaryMessageVariables={summaryMessageVariables}
notifyWhenSelectOptions={notifyWhenSelectOptions}
defaultNotifyWhenValue={defaultNotifyWhenValue}
producerId="infrastructure"
featureId="infrastructure"
/>
);
}
Expand Up @@ -8,6 +8,7 @@
import React, { Suspense, useEffect, useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ValidFeatureId, AlertConsumers } from '@kbn/rule-data-utils';
import {
EuiFlexGroup,
EuiFlexItem,
Expand Down Expand Up @@ -87,7 +88,10 @@ export type ActionTypeFormProps = {
minimumThrottleInterval?: [number | undefined, string];
notifyWhenSelectOptions?: NotifyWhenSelectOptions[];
defaultNotifyWhenValue?: RuleNotifyWhenType;
showActionAlertsFilter?: boolean;
featureId: string;
producerId: string;
ruleTypeId?: string;
hasFieldsForAAD?: boolean;
} & Pick<
ActionAccordionFormProps,
| 'defaultActionGroupId'
Expand Down Expand Up @@ -134,7 +138,10 @@ export const ActionTypeForm = ({
minimumThrottleInterval,
notifyWhenSelectOptions,
defaultNotifyWhenValue,
showActionAlertsFilter,
producerId,
featureId,
ruleTypeId,
hasFieldsForAAD,
}: ActionTypeFormProps) => {
const {
application: { capabilities },
Expand Down Expand Up @@ -333,6 +340,8 @@ export const ActionTypeForm = ({
setActionGroupIdByIndex &&
!actionItem.frequency?.summary;

const showActionAlertsFilter = hasFieldsForAAD || producerId === AlertConsumers.SIEM;

const accordionContent = checkEnabledResult.isEnabled ? (
<>
<EuiSplitPanel.Inner
Expand Down Expand Up @@ -418,6 +427,9 @@ export const ActionTypeForm = ({
<ActionAlertsFilterQuery
state={actionItem.alertsFilter?.query}
onChange={(query) => setActionAlertsFilterProperty('query', query, index)}
featureIds={[producerId as ValidFeatureId]}
appName={featureId!}
ruleTypeId={ruleTypeId}
/>
<EuiSpacer size="s" />
<ActionAlertsFilterTimeframe
Expand Down

0 comments on commit 0af4823

Please sign in to comment.