[ResponseOps][Alerts] Remove fieldsForAad attribute from rule type registry (#215767)

## Summary

Implements https://github.com/elastic/kibana/issues/215338

This PR removes `fieldsForAAD ` and `hasFieldsForAAD ` from the rule
type registry and all corresponding rule types.
It uses existing `getBrowserFieldsByFeatureId ` route to fetch fields
from
[field_caps](https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-field-caps.html)
API.

It also updates `getBrowserFieldsByFeatureId` route 
- to use `include_empty_fields: false` query param to fetch only non
empty fields to have limited number of fields
- to use `index filter` to fetch fields only from last 90 days for
better performance
    ```
       const indexFilter = {
              range: {
                '@timestamp': {
                  gte: 'now-90d',
                },
              },
            };
    ```   


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [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

### Flaky Test runner: 
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8151

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Janki Salvi 2025-04-09 11:05:05 +01:00 committed by GitHub
parent 102ceb65e3
commit adbc6d9a38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 775 additions and 1028 deletions

View file

@ -53049,7 +53049,7 @@ components:
does_set_recovery_context: true
enabled_in_license: true
has_alerts_mappings: true
has_fields_for_a_a_d: false
has_fields_for_a_a_d: true
id: xpack.ml.anomaly_detection_alert
is_exportable: true
minimum_license_required: platinum

View file

@ -8,19 +8,16 @@
*/
import { httpServiceMock } from '@kbn/core/public/mocks';
import {
fetchRuleTypeAadTemplateFields,
getDescription,
} from './fetch_rule_type_aad_template_fields';
import { fetchRuleTypeAlertFields, getDescription } from './fetch_rule_type_alert_fields';
import type { EcsMetadata } from '@kbn/alerts-as-data-utils/src/field_maps/types';
const http = httpServiceMock.createStartContract();
describe('fetchRuleTypeAadTemplateFields', () => {
describe('fetchRuleTypeAlertFields', () => {
test('should call aad fields endpoint with the correct params', async () => {
http.get.mockResolvedValueOnce(['mockData']);
const result = await fetchRuleTypeAadTemplateFields({
const result = await fetchRuleTypeAlertFields({
http,
ruleTypeId: 'test-rule-type-id',
});
@ -28,10 +25,12 @@ describe('fetchRuleTypeAadTemplateFields', () => {
expect(result).toEqual(['mockData']);
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/rac/alerts/aad_fields",
"/internal/rac/alerts/browser_fields",
Object {
"query": Object {
"ruleTypeId": "test-rule-type-id",
"ruleTypeIds": Array [
"test-rule-type-id",
],
},
},
]

View file

@ -21,7 +21,7 @@ export const getDescription = (fieldName: string, ecsFlat: Record<string, EcsMet
return ecsField?.description ?? '';
};
export const fetchRuleTypeAadTemplateFields = async ({
export const fetchRuleTypeAlertFields = async ({
http,
ruleTypeId,
}: {
@ -29,8 +29,8 @@ export const fetchRuleTypeAadTemplateFields = async ({
ruleTypeId?: string;
}): Promise<DataViewField[]> => {
if (!ruleTypeId) return EMPTY_AAD_FIELDS;
const fields = await http.get<DataViewField[]>(`${BASE_RAC_ALERTS_API_PATH}/aad_fields`, {
query: { ruleTypeId },
const fields = await http.get<DataViewField[]>(`${BASE_RAC_ALERTS_API_PATH}/browser_fields`, {
query: { ruleTypeIds: [ruleTypeId] },
});
return fields;

View file

@ -7,4 +7,4 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export * from './fetch_rule_type_aad_template_fields';
export * from './fetch_rule_type_alert_fields';

View file

@ -12,5 +12,5 @@ export * from './fetch_alerts_fields';
export * from './fetch_alerts_index_names';
export * from './fetch_connectors';
export * from './fetch_connector_types';
export * from './fetch_rule_type_aad_template_fields';
export * from './fetch_rule_type_alert_fields';
export * from './fetch_ui_health_status';

View file

@ -31,7 +31,6 @@ export interface RuleType<
actionVariables: ActionVariables;
authorizedConsumers: Record<string, { read: boolean; all: boolean }>;
enabledInLicense: boolean;
hasFieldsForAAD?: boolean;
hasAlertsMappings?: boolean;
}

View file

@ -28,7 +28,6 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
does_set_recovery_context: doesSetRecoveryContext,
default_schedule_interval: defaultScheduleInterval,
has_alerts_mappings: hasAlertsMappings,
has_fields_for_a_a_d: hasFieldsForAAD,
is_exportable: isExportable,
...rest
}: AsApiContract<RuleType>) => ({
@ -43,7 +42,6 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
doesSetRecoveryContext,
defaultScheduleInterval,
hasAlertsMappings,
hasFieldsForAAD,
isExportable,
...rest,
});

View file

@ -13,3 +13,4 @@ export * from './use_load_connectors';
export * from './use_load_ui_config';
export * from './use_resolve_rule';
export * from './use_update_rule';
export * from './use_load_rule_type_alert_fields';

View file

@ -13,7 +13,7 @@ import { waitFor, renderHook } from '@testing-library/react';
import { httpServiceMock } from '@kbn/core/public/mocks';
import { EcsFlat } from '@elastic/ecs';
import { useLoadRuleTypeAadTemplateField } from './use_load_rule_type_aad_template_fields';
import { useLoadRuleTypeAlertFields } from './use_load_rule_type_alert_fields';
const queryClient = new QueryClient();
@ -31,7 +31,7 @@ const fieldsMetadataMock = {
),
};
describe('useLoadRuleTypeAadTemplateFields', () => {
describe('useLoadRuleTypeAlertFields', () => {
beforeEach(() => {
http.get.mockResolvedValue([
{
@ -50,7 +50,7 @@ describe('useLoadRuleTypeAadTemplateFields', () => {
test('should call API endpoint with the correct parameters', async () => {
const { result } = renderHook(
() =>
useLoadRuleTypeAadTemplateField({
useLoadRuleTypeAlertFields({
http,
fieldsMetadata: fieldsMetadataMock,
ruleTypeId: 'ruleTypeId',
@ -63,8 +63,8 @@ describe('useLoadRuleTypeAadTemplateFields', () => {
return expect(result.current.isInitialLoading).toEqual(false);
});
expect(http.get).toHaveBeenLastCalledWith('/internal/rac/alerts/aad_fields', {
query: { ruleTypeId: 'ruleTypeId' },
expect(http.get).toHaveBeenLastCalledWith('/internal/rac/alerts/browser_fields', {
query: { ruleTypeIds: ['ruleTypeId'] },
});
expect(result.current.data).toMatchInlineSnapshot(`

View file

@ -12,13 +12,10 @@ import { useRef } from 'react';
import { isEmpty } from 'lodash';
import type { HttpStart } from '@kbn/core-http-browser';
import { useQuery } from '@tanstack/react-query';
import {
fetchRuleTypeAadTemplateFields,
getDescription,
} from '@kbn/alerts-ui-shared/src/common/apis';
import { fetchRuleTypeAlertFields, getDescription } from '@kbn/alerts-ui-shared/src/common/apis';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
export interface UseLoadRuleTypeAadTemplateFieldProps {
export interface UseLoadRuleTypeAlertFieldsProps {
http: HttpStart;
ruleTypeId?: string;
enabled: boolean;
@ -26,7 +23,7 @@ export interface UseLoadRuleTypeAadTemplateFieldProps {
fieldsMetadata?: FieldsMetadataPublicStart;
}
export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplateFieldProps) => {
export const useLoadRuleTypeAlertFields = (props: UseLoadRuleTypeAlertFieldsProps) => {
const ecsFlat = useRef<Record<string, any>>({});
const { http, ruleTypeId, enabled, cacheTime, fieldsMetadata } = props;
@ -43,7 +40,7 @@ export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplat
}
}
return fetchRuleTypeAadTemplateFields({ http, ruleTypeId });
return fetchRuleTypeAlertFields({ http, ruleTypeId });
};
const {
@ -52,7 +49,7 @@ export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplat
isFetching,
isInitialLoading,
} = useQuery({
queryKey: ['useLoadRuleTypeAadTemplateField', ruleTypeId],
queryKey: ['useLoadRuleTypeAlertFields', ruleTypeId],
queryFn,
select: (dataViewFields) => {
return dataViewFields?.map<ActionVariable>((d) => ({

View file

@ -109,7 +109,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
healthCheckError,
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
flappingSettings,
} = useLoadDependencies({
http,
@ -194,7 +194,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
plugins,
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
selectedRuleTypeModel: ruleTypeModel,
selectedRuleType: ruleType,

View file

@ -88,7 +88,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
fetchedFormData,
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
flappingSettings,
} = useLoadDependencies({
http,
@ -195,7 +195,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
initialRuleFormState={{
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
formData: {
...getDefaultFormData({
ruleTypeId: fetchedFormData.ruleTypeId,

View file

@ -41,8 +41,8 @@ jest.mock('../common/hooks/use_load_connector_types', () => ({
useLoadConnectorTypes: jest.fn(),
}));
jest.mock('../common/hooks/use_load_rule_type_aad_template_fields', () => ({
useLoadRuleTypeAadTemplateField: jest.fn(),
jest.mock('../common/hooks/use_load_rule_type_alert_fields', () => ({
useLoadRuleTypeAlertFields: jest.fn(),
}));
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings', () => ({
@ -56,8 +56,8 @@ const { useHealthCheck } = jest.requireMock(
const { useResolveRule } = jest.requireMock('../common/hooks/use_resolve_rule');
const { useLoadConnectors } = jest.requireMock('../common/hooks/use_load_connectors');
const { useLoadConnectorTypes } = jest.requireMock('../common/hooks/use_load_connector_types');
const { useLoadRuleTypeAadTemplateField } = jest.requireMock(
'../common/hooks/use_load_rule_type_aad_template_fields'
const { useLoadRuleTypeAlertFields } = jest.requireMock(
'../common/hooks/use_load_rule_type_alert_fields'
);
const { useGetRuleTypesPermissions } = jest.requireMock(
'@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions'
@ -135,7 +135,6 @@ const indexThresholdRuleType = {
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: '.index-threshold',
name: 'Index threshold',
category: 'management',
@ -190,7 +189,7 @@ const mockConnectorType = {
is_system_action_type: false,
};
const mockAadTemplateField = {
const mockAlertField = {
name: '@timestamp',
deprecated: false,
useWithTripleBracesInTemplates: false,
@ -209,8 +208,8 @@ useLoadConnectorTypes.mockReturnValue({
isInitialLoading: false,
});
useLoadRuleTypeAadTemplateField.mockReturnValue({
data: [mockAadTemplateField],
useLoadRuleTypeAlertFields.mockReturnValue({
data: [mockAlertField],
isLoading: false,
isInitialLoading: false,
});
@ -274,7 +273,7 @@ describe('useLoadDependencies', () => {
},
connectors: [mockConnector],
connectorTypes: [mockConnectorType],
aadTemplateFields: [mockAadTemplateField],
alertFields: [mockAlertField],
});
});

View file

@ -23,10 +23,10 @@ import {
useLoadConnectorTypes,
useLoadUiConfig,
useResolveRule,
useLoadRuleTypeAlertFields,
} from '../common/hooks';
import type { RuleTypeRegistryContract } from '../common/types';
import { IS_RULE_SPECIFIC_FLAPPING_ENABLED } from '../constants';
import { useLoadRuleTypeAadTemplateField } from '../common/hooks/use_load_rule_type_aad_template_fields';
export interface UseLoadDependencies {
http: HttpStart;
@ -124,10 +124,10 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
});
const {
data: aadTemplateFields,
isLoading: isLoadingAadtemplateFields,
isInitialLoading: isInitialLoadingAadTemplateField,
} = useLoadRuleTypeAadTemplateField({
data: alertFields,
isLoading: isLoadingAlertFields,
isInitialLoading: isInitialLoadingAlertFields,
} = useLoadRuleTypeAlertFields({
http,
ruleTypeId: computedRuleTypeId,
enabled: !!computedRuleTypeId && canReadConnectors,
@ -159,7 +159,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isLoadingFlappingSettings ||
isLoadingConnectors ||
isLoadingConnectorTypes ||
isLoadingAadtemplateFields
isLoadingAlertFields
);
}
@ -172,7 +172,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isLoadingFlappingSettings ||
isLoadingConnectors ||
isLoadingConnectorTypes ||
isLoadingAadtemplateFields
isLoadingAlertFields
);
}, [
id,
@ -183,7 +183,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isLoadingFlappingSettings,
isLoadingConnectors,
isLoadingConnectorTypes,
isLoadingAadtemplateFields,
isLoadingAlertFields,
]);
const isInitialLoading = useMemo(() => {
@ -196,7 +196,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isInitialLoadingFlappingSettings ||
isInitialLoadingConnectors ||
isInitialLoadingConnectorTypes ||
isInitialLoadingAadTemplateField
isInitialLoadingAlertFields
);
}
@ -209,7 +209,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isInitialLoadingFlappingSettings ||
isInitialLoadingConnectors ||
isInitialLoadingConnectorTypes ||
isInitialLoadingAadTemplateField
isInitialLoadingAlertFields
);
}, [
id,
@ -220,7 +220,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
isInitialLoadingFlappingSettings,
isInitialLoadingConnectors,
isInitialLoadingConnectorTypes,
isInitialLoadingAadTemplateField,
isInitialLoadingAlertFields,
]);
return {
@ -235,6 +235,6 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
flappingSettings,
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
};
};

View file

@ -105,7 +105,7 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
selectedRuleTypeModel,
connectors,
connectorTypes,
aadTemplateFields,
alertFields,
} = useRuleFormState();
const [tab, setTab] = useState<string>(MESSAGES_TAB);
@ -159,9 +159,7 @@ export const RuleActionsItem = (props: RuleActionsItemProps) => {
ruleTypeModel: selectedRuleTypeModel,
});
const templateFields = action.useAlertDataForTemplate
? aadTemplateFields
: availableActionVariables;
const templateFields = action.useAlertDataForTemplate ? alertFields : availableActionVariables;
const checkEnabledResult = useMemo(() => {
if (!actionType) {

View file

@ -327,7 +327,7 @@ describe('ruleActionsSettings', () => {
validConsumers: ['stackAlerts', 'logs'],
selectedRuleType: {
...ruleType,
hasFieldsForAAD: true,
hasAlertsMappings: true,
},
selectedRuleTypeModel: ruleModel,
});
@ -360,7 +360,7 @@ describe('ruleActionsSettings', () => {
validConsumers: ['stackAlerts', 'logs'],
selectedRuleType: {
...ruleType,
hasFieldsForAAD: true,
hasAlertsMappings: true,
},
selectedRuleTypeModel: ruleModel,
});
@ -402,7 +402,7 @@ describe('ruleActionsSettings', () => {
validConsumers: ['stackAlerts', 'logs'],
selectedRuleType: {
...ruleType,
hasFieldsForAAD: true,
hasAlertsMappings: true,
},
selectedRuleTypeModel: ruleModel,
});

View file

@ -24,7 +24,7 @@ import {
getDurationNumberInItsUnit,
getDurationUnitValue,
getSelectedActionGroup,
hasFieldsForAad,
hasAlertsFields,
parseDuration,
} from '../utils';
import { DEFAULT_VALID_CONSUMERS } from '../constants';
@ -191,7 +191,7 @@ export const RuleActionsSettings = (props: RuleActionsSettingsProps) => {
const ruleTypeId = selectedRuleType.id;
const showActionAlertsFilter =
hasFieldsForAad({
hasAlertsFields({
ruleType: selectedRuleType,
consumer,
validConsumers,

View file

@ -60,7 +60,7 @@ interface SystemActionAccordionContentProps extends RuleActionsSystemActionsItem
const SystemActionAccordionContent: React.FC<SystemActionAccordionContentProps> = React.memo(
({ connector, checkEnabledResult, action, index, producerId, warning, onParamsChange }) => {
const { aadTemplateFields } = useRuleFormState();
const { alertFields } = useRuleFormState();
const { euiTheme } = useEuiTheme();
const plain = useEuiBackgroundColor('plain');
@ -100,7 +100,7 @@ const SystemActionAccordionContent: React.FC<SystemActionAccordionContentProps>
connector={connector}
producerId={producerId}
warning={warning}
templateFields={aadTemplateFields}
templateFields={alertFields}
onParamsChange={onParamsChange}
/>
</EuiFlexItem>

View file

@ -41,7 +41,6 @@ const indexThresholdRuleType = {
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: '.index-threshold',
name: 'Index threshold',
category: 'management',
@ -80,7 +79,7 @@ const initialState: RuleFormState = {
validConsumers: [],
connectors: [],
connectorTypes: [],
aadTemplateFields: [],
alertFields: [],
};
describe('ruleFormStateReducer', () => {

View file

@ -81,7 +81,7 @@ export interface RuleFormState<
plugins: RuleFormPlugins;
connectors: ActionConnector[];
connectorTypes: ActionType[];
aadTemplateFields: ActionVariable[];
alertFields: ActionVariable[];
availableRuleTypes: RuleTypeWithDescription[];
baseErrors?: RuleFormBaseErrors;
paramsErrors?: RuleFormParamsErrors;

View file

@ -9,25 +9,12 @@
import { AlertConsumers } from '@kbn/rule-data-utils';
import type { RuleTypeWithDescription } from '../common/types';
import { hasFieldsForAad } from './has_fields_for_aad';
describe('hasFieldsForAad', () => {
test('should return true if alert has fields for add', () => {
const hasFields = hasFieldsForAad({
ruleType: {
hasFieldsForAAD: true,
} as RuleTypeWithDescription,
consumer: 'stackAlerts',
validConsumers: [],
});
expect(hasFields).toBeTruthy();
});
import { hasAlertsFields } from './has_alerts_fields';
describe('hasAlertsFields', () => {
test('should return true if producer is SIEM', () => {
const hasFields = hasFieldsForAad({
const hasFields = hasAlertsFields({
ruleType: {
hasFieldsForAAD: false,
producer: AlertConsumers.SIEM,
} as RuleTypeWithDescription,
consumer: 'stackAlerts',
@ -38,9 +25,8 @@ describe('hasFieldsForAad', () => {
});
test('should return true if has alerts mappings', () => {
const hasFields = hasFieldsForAad({
const hasFields = hasAlertsFields({
ruleType: {
hasFieldsForAAD: false,
hasAlertsMappings: true,
} as RuleTypeWithDescription,
consumer: 'stackAlerts',

View file

@ -11,7 +11,7 @@ import type { RuleCreationValidConsumer } from '@kbn/rule-data-utils';
import { AlertConsumers } from '@kbn/rule-data-utils';
import type { RuleTypeWithDescription } from '../common/types';
export const hasFieldsForAad = ({
export const hasAlertsFields = ({
ruleType,
consumer,
validConsumers,
@ -21,9 +21,7 @@ export const hasFieldsForAad = ({
validConsumers: RuleCreationValidConsumer[];
}) => {
const hasAlertHasData = ruleType
? ruleType.hasFieldsForAAD ||
ruleType.producer === AlertConsumers.SIEM ||
ruleType.hasAlertsMappings
? ruleType.producer === AlertConsumers.SIEM || ruleType.hasAlertsMappings
: false;
return !!hasAlertHasData;

View file

@ -14,7 +14,7 @@ export * from './get_authorized_rule_types';
export * from './get_authorized_consumers';
export * from './get_initial_multi_consumer';
export * from './get_initial_schedule';
export * from './has_fields_for_aad';
export * from './has_alerts_fields';
export * from './get_selected_action_group';
export * from './get_initial_consumer';
export * from './get_default_params';

View file

@ -33,7 +33,6 @@ const ruleTypeResponse = {
does_set_recovery_context: false,
enabled_in_license: true,
has_alerts_mappings: true,
has_fields_for_a_a_d: false,
is_exportable: true,
minimum_license_required: 'basic',
producer: 'test',
@ -66,7 +65,6 @@ const expectedRuleType = {
doesSetRecoveryContext: false,
enabledInLicense: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
isExportable: true,
minimumLicenseRequired: 'basic',
producer: 'test',

View file

@ -33,7 +33,6 @@ const rewriteRuleType: RewriteRequestCase<InternalRuleType> = ({
does_set_recovery_context: doesSetRecoveryContext,
default_schedule_interval: defaultScheduleInterval,
has_alerts_mappings: hasAlertsMappings,
has_fields_for_a_a_d: hasFieldsForAAD,
is_exportable: isExportable,
...rest
}: AsApiContract<InternalRuleType>) => ({
@ -48,7 +47,6 @@ const rewriteRuleType: RewriteRequestCase<InternalRuleType> = ({
doesSetRecoveryContext,
defaultScheduleInterval,
hasAlertsMappings,
hasFieldsForAAD,
isExportable,
...rest,
});

View file

@ -33,7 +33,6 @@ const ruleTypeResponse = {
does_set_recovery_context: false,
enabled_in_license: true,
has_alerts_mappings: true,
has_fields_for_a_a_d: false,
is_exportable: true,
minimum_license_required: 'basic',
producer: 'test',
@ -65,7 +64,6 @@ const expectedRuleType = {
doesSetRecoveryContext: false,
enabledInLicense: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
isExportable: true,
minimumLicenseRequired: 'basic',
producer: 'test',

View file

@ -28,7 +28,6 @@ const rewriteRuleType: RewriteRequestCase<RuleType> = ({
does_set_recovery_context: doesSetRecoveryContext,
default_schedule_interval: defaultScheduleInterval,
has_alerts_mappings: hasAlertsMappings,
has_fields_for_a_a_d: hasFieldsForAAD,
is_exportable: isExportable,
...rest
}: AsApiContract<RuleType>) => ({
@ -43,7 +42,6 @@ const rewriteRuleType: RewriteRequestCase<RuleType> = ({
doesSetRecoveryContext,
defaultScheduleInterval,
hasAlertsMappings,
hasFieldsForAAD,
isExportable,
...rest,
});

View file

@ -11,6 +11,7 @@
import React, { Fragment, lazy } from 'react';
import { nextTick } from '@kbn/test-jest-helpers';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { coreMock } from '@kbn/core/public/mocks';
@ -155,27 +156,29 @@ describe('alert_form', () => {
const actionWrapper = mount(
<I18nProvider>
<KibanaReactContext.Provider>
<ActionForm
actions={initialAlert.actions}
defaultActionGroupId={'default'}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;
}}
setActions={() => {}}
setActionParamsProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionFrequencyProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionAlertsFilterProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
actionTypeRegistry={actionTypeRegistry}
featureId="alerting"
producerId="alerting"
ruleTypeId=".es-query"
/>
<QueryClientProvider client={new QueryClient()}>
<ActionForm
actions={initialAlert.actions}
defaultActionGroupId={'default'}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;
}}
setActions={() => {}}
setActionParamsProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionFrequencyProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionAlertsFilterProperty={(key: string, value: unknown, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
actionTypeRegistry={actionTypeRegistry}
featureId="alerting"
producerId="alerting"
ruleTypeId=".es-query"
/>
</QueryClientProvider>
</KibanaReactContext.Provider>
</I18nProvider>
);

View file

@ -7,13 +7,13 @@
import { schema } from '@kbn/config-schema';
const actionVariableSchema = schema.object({
export const actionVariableSchema = schema.object({
name: schema.string(),
description: schema.string(),
usesPublicBaseUrl: schema.maybe(schema.boolean()),
});
const actionGroupSchema = schema.object(
export const actionGroupSchema = schema.object(
{
id: schema.string(),
name: schema.string(),

View file

@ -6,10 +6,10 @@
*/
import { schema } from '@kbn/config-schema';
import { typesRulesSchema } from '../../external/schemas/v1';
import { actionGroupSchema, actionVariableSchema } from '../../external/schemas/v1';
export const getRuleTypesInternalResponseBodySchema = schema.arrayOf(
typesRulesSchema.extends({
schema.object({
solution: schema.oneOf(
[schema.literal('stack'), schema.literal('observability'), schema.literal('security')],
{
@ -18,6 +18,155 @@ export const getRuleTypesInternalResponseBodySchema = schema.arrayOf(
},
}
),
action_groups: schema.maybe(
schema.arrayOf(actionGroupSchema, {
meta: {
description:
"An explicit list of groups for which the rule type can schedule actions, each with the action group's unique ID and human readable name. Rule actions validation uses this configuration to ensure that groups are valid.",
},
})
),
action_variables: schema.maybe(
schema.object(
{
context: schema.maybe(schema.arrayOf(actionVariableSchema)),
state: schema.maybe(schema.arrayOf(actionVariableSchema)),
params: schema.maybe(schema.arrayOf(actionVariableSchema)),
},
{
meta: {
description:
'A list of action variables that the rule type makes available via context and state in action parameter templates, and a short human readable description. When you create a rule in Kibana, it uses this information to prompt you for these variables in action parameter editors.',
},
}
)
),
alerts: schema.maybe(
schema.object(
{
context: schema.string({
meta: {
description: 'The namespace for this rule type.',
},
}),
mappings: schema.maybe(
schema.object({
dynamic: schema.maybe(
schema.oneOf([schema.literal(false), schema.literal('strict')], {
meta: {
description: 'Indicates whether new fields are added dynamically.',
},
})
),
fieldMap: schema.recordOf(schema.string(), schema.any(), {
meta: {
description:
'Mapping information for each field supported in alerts as data documents for this rule type. For more information about mapping parameters, refer to the Elasticsearch documentation.',
},
}),
shouldWrite: schema.maybe(
schema.boolean({
meta: {
description: 'Indicates whether the rule should write out alerts as data.',
},
})
),
useEcs: schema.maybe(
schema.boolean({
meta: {
description:
'Indicates whether to include the ECS component template for the alerts.',
},
})
),
})
),
},
{
meta: {
description: 'Details for writing alerts as data documents for this rule type.',
},
}
)
),
authorized_consumers: schema.recordOf(
schema.string(),
schema.object({ read: schema.boolean(), all: schema.boolean() }),
{
meta: {
description: 'The list of the plugins IDs that have access to the rule type.',
},
}
),
category: schema.string({
meta: {
description:
'The rule category, which is used by features such as category-specific maintenance windows.',
},
}),
default_action_group_id: schema.string({
meta: {
description: 'The default identifier for the rule type group.',
},
}),
default_schedule_interval: schema.maybe(schema.string()),
does_set_recovery_context: schema.maybe(
schema.boolean({
meta: {
description:
'Indicates whether the rule passes context variables to its recovery action.',
},
})
),
enabled_in_license: schema.boolean({
meta: {
description:
'Indicates whether the rule type is enabled or disabled based on the subscription.',
},
}),
has_alerts_mappings: schema.boolean({
meta: {
description: 'Indicates whether the rule type has custom mappings for the alert data.',
},
}),
id: schema.string({
meta: {
description: 'The unique identifier for the rule type.',
},
}),
is_exportable: schema.boolean({
meta: {
description:
'Indicates whether the rule type is exportable in Stack Management > Saved Objects.',
},
}),
minimum_license_required: schema.oneOf(
[
schema.literal('basic'),
schema.literal('gold'),
schema.literal('platinum'),
schema.literal('standard'),
schema.literal('enterprise'),
schema.literal('trial'),
],
{
meta: {
description: 'The subscriptions required to use the rule type.',
},
}
),
name: schema.string({
meta: {
description: 'The descriptive name of the rule type.',
},
}),
producer: schema.string({
meta: {
description: 'An identifier for the application that produces this rule type.',
},
}),
recovery_action_group: actionGroupSchema,
rule_task_timeout: schema.maybe(schema.string()),
})
);

View file

@ -753,7 +753,7 @@
},
"does_set_recovery_context": true,
"has_alerts_mappings": true,
"has_fields_for_a_a_d": false
"has_fields_for_a_a_d": true
},
{
"id": "xpack.ml.anomaly_detection_jobs_health",

View file

@ -528,7 +528,7 @@ components:
all: true
does_set_recovery_context: true
has_alerts_mappings: true
has_fields_for_a_a_d: false
has_fields_for_a_a_d: true
- id: xpack.ml.anomaly_detection_jobs_health
name: Anomaly detection jobs health
category: management

View file

@ -2,321 +2,172 @@ summary: Retrieve rule types associated with Kibana machine learning features
value:
[
{
"id": "xpack.ml.anomaly_detection_alert",
"name": "Anomaly detection alert",
"category": "management",
"producer": "ml",
"alerts": {
"context": "ml.anomaly-detection",
"mappings": {
"fieldMap": {
"kibana.alert.job_id": {
"type": "keyword",
"array": false,
"required": true
},
"kibana.alert.anomaly_score": {
"type": "double",
"array": false,
"required": false
},
"kibana.alert.is_interim": {
"type": "boolean",
"array": false,
"required": false
},
"kibana.alert.anomaly_timestamp": {
"type": "date",
"array": false,
"required": false
},
"kibana.alert.top_records": {
"type": "object",
"array": true,
"required": false,
"dynamic": false,
"properties": {
"job_id": {
"type": "keyword"
},
"record_score": {
"type": "double"
},
"initial_record_score": {
"type": "double"
},
"detector_index": {
"type": "integer"
},
"is_interim": {
"type": "boolean"
},
"timestamp": {
"type": "date"
},
"partition_field_name": {
"type": "keyword"
},
"partition_field_value": {
"type": "keyword"
},
"over_field_name": {
"type": "keyword"
},
"over_field_value": {
"type": "keyword"
},
"by_field_name": {
"type": "keyword"
},
"by_field_value": {
"type": "keyword"
},
"function": {
"type": "keyword"
},
"typical": {
"type": "double"
},
"actual": {
"type": "double"
},
"field_name": {
"type": "keyword"
}
}
},
"kibana.alert.top_influencers": {
"type": "object",
"array": true,
"required": false,
"dynamic": false,
"properties": {
"job_id": {
"type": "keyword"
},
"influencer_field_name": {
"type": "keyword"
},
"influencer_field_value": {
"type": "keyword"
},
"influencer_score": {
"type": "double"
},
"initial_influencer_score": {
"type": "double"
},
"is_interim": {
"type": "boolean"
},
"timestamp": {
"type": "date"
}
}
}
}
},
"shouldWrite": true
},
"enabled_in_license": true,
"recovery_action_group": {
"id": "recovered",
"name": "Recovered"
},
"action_groups": [
'id': 'xpack.ml.anomaly_detection_alert',
'name': 'Anomaly detection alert',
'category': 'management',
'producer': 'ml',
'alerts':
{
"id": "anomaly_score_match",
"name": "Anomaly score matched the condition"
'context': 'ml.anomaly-detection',
'mappings':
{
'fieldMap':
{
'kibana.alert.job_id': { 'type': 'keyword', 'array': false, 'required': true },
'kibana.alert.anomaly_score':
{ 'type': 'double', 'array': false, 'required': false },
'kibana.alert.is_interim':
{ 'type': 'boolean', 'array': false, 'required': false },
'kibana.alert.anomaly_timestamp':
{ 'type': 'date', 'array': false, 'required': false },
'kibana.alert.top_records':
{
'type': 'object',
'array': true,
'required': false,
'dynamic': false,
'properties':
{
'job_id': { 'type': 'keyword' },
'record_score': { 'type': 'double' },
'initial_record_score': { 'type': 'double' },
'detector_index': { 'type': 'integer' },
'is_interim': { 'type': 'boolean' },
'timestamp': { 'type': 'date' },
'partition_field_name': { 'type': 'keyword' },
'partition_field_value': { 'type': 'keyword' },
'over_field_name': { 'type': 'keyword' },
'over_field_value': { 'type': 'keyword' },
'by_field_name': { 'type': 'keyword' },
'by_field_value': { 'type': 'keyword' },
'function': { 'type': 'keyword' },
'typical': { 'type': 'double' },
'actual': { 'type': 'double' },
'field_name': { 'type': 'keyword' },
},
},
'kibana.alert.top_influencers':
{
'type': 'object',
'array': true,
'required': false,
'dynamic': false,
'properties':
{
'job_id': { 'type': 'keyword' },
'influencer_field_name': { 'type': 'keyword' },
'influencer_field_value': { 'type': 'keyword' },
'influencer_score': { 'type': 'double' },
'initial_influencer_score': { 'type': 'double' },
'is_interim': { 'type': 'boolean' },
'timestamp': { 'type': 'date' },
},
},
},
},
'shouldWrite': true,
},
{
"id": "recovered",
"name": "Recovered"
}
],
"default_action_group_id": "anomaly_score_match",
"minimum_license_required": "platinum",
"is_exportable": true,
"rule_task_timeout": "5m",
"action_variables": {
"context": [
{
"name": "timestamp",
"description": "The bucket timestamp of the anomaly"
},
{
"name": "timestampIso8601",
"description": "The bucket time of the anomaly in ISO8601 format"
},
{
"name": "jobIds",
"description": "List of job IDs that triggered the alert"
},
{
"name": "message",
"description": "Alert info message"
},
{
"name": "isInterim",
"description": "Indicate if top hits contain interim results"
},
{
"name": "score",
"description": "Anomaly score at the time of the notification action"
},
{
"name": "topRecords",
"description": "Top records"
},
{
"name": "topInfluencers",
"description": "Top influencers"
},
{
"name": "anomalyExplorerUrl",
"description": "URL to open in the Anomaly Explorer",
"useWithTripleBracesInTemplates": true
}
'enabled_in_license': true,
'recovery_action_group': { 'id': 'recovered', 'name': 'Recovered' },
'action_groups':
[
{ 'id': 'anomaly_score_match', 'name': 'Anomaly score matched the condition' },
{ 'id': 'recovered', 'name': 'Recovered' },
],
"state": [],
"params": []
},
"authorized_consumers": {
"alerts": {
"read": true,
"all": true
'default_action_group_id': 'anomaly_score_match',
'minimum_license_required': 'platinum',
'is_exportable': true,
'rule_task_timeout': '5m',
'action_variables':
{
'context':
[
{ 'name': 'timestamp', 'description': 'The bucket timestamp of the anomaly' },
{
'name': 'timestampIso8601',
'description': 'The bucket time of the anomaly in ISO8601 format',
},
{ 'name': 'jobIds', 'description': 'List of job IDs that triggered the alert' },
{ 'name': 'message', 'description': 'Alert info message' },
{
'name': 'isInterim',
'description': 'Indicate if top hits contain interim results',
},
{
'name': 'score',
'description': 'Anomaly score at the time of the notification action',
},
{ 'name': 'topRecords', 'description': 'Top records' },
{ 'name': 'topInfluencers', 'description': 'Top influencers' },
{
'name': 'anomalyExplorerUrl',
'description': 'URL to open in the Anomaly Explorer',
'useWithTripleBracesInTemplates': true,
},
],
'state': [],
'params': [],
},
"stackAlerts": {
"read": true,
"all": true
'authorized_consumers':
{
'alerts': { 'read': true, 'all': true },
'stackAlerts': { 'read': true, 'all': true },
'slo': { 'read': true, 'all': true },
'ml': { 'read': true, 'all': true },
'uptime': { 'read': true, 'all': true },
'infrastructure': { 'read': true, 'all': true },
'logs': { 'read': true, 'all': true },
'monitoring': { 'read': true, 'all': true },
'siem': { 'read': true, 'all': true },
'apm': { 'read': true, 'all': true },
'discover': { 'read': true, 'all': true },
},
"slo": {
"read": true,
"all": true
},
"ml": {
"read": true,
"all": true
},
"uptime": {
"read": true,
"all": true
},
"infrastructure": {
"read": true,
"all": true
},
"logs": {
"read": true,
"all": true
},
"monitoring": {
"read": true,
"all": true
},
"siem": {
"read": true,
"all": true
},
"apm": {
"read": true,
"all": true
},
"discover": {
"read": true,
"all": true
}
},
"does_set_recovery_context": true,
"has_alerts_mappings": true,
"has_fields_for_a_a_d": false
'does_set_recovery_context': true,
'has_alerts_mappings': true,
'has_fields_for_a_a_d': true,
},
{
"id": "xpack.ml.anomaly_detection_jobs_health",
"name": "Anomaly detection jobs health",
"category": "management",
"producer": "ml",
"enabled_in_license": true,
"recovery_action_group": {
"id": "recovered",
"name": "Recovered"
},
"action_groups": [
{
"id": "anomaly_detection_realtime_issue",
"name": "Issue detected"
},
{
"id": "recovered",
"name": "Recovered"
}
],
"default_action_group_id": "anomaly_detection_realtime_issue",
"minimum_license_required": "platinum",
"is_exportable": true,
"rule_task_timeout": "5m",
"action_variables": {
"context": [
{
"name": "results",
"description": "Results of the rule execution"
},
{
"name": "message",
"description": "Alert info message"
}
'id': 'xpack.ml.anomaly_detection_jobs_health',
'name': 'Anomaly detection jobs health',
'category': 'management',
'producer': 'ml',
'enabled_in_license': true,
'recovery_action_group': { 'id': 'recovered', 'name': 'Recovered' },
'action_groups':
[
{ 'id': 'anomaly_detection_realtime_issue', 'name': 'Issue detected' },
{ 'id': 'recovered', 'name': 'Recovered' },
],
"state": [],
"params": []
},
"authorized_consumers": {
"alerts": {
"read": true,
"all": true
'default_action_group_id': 'anomaly_detection_realtime_issue',
'minimum_license_required': 'platinum',
'is_exportable': true,
'rule_task_timeout': '5m',
'action_variables':
{
'context':
[
{ 'name': 'results', 'description': 'Results of the rule execution' },
{ 'name': 'message', 'description': 'Alert info message' },
],
'state': [],
'params': [],
},
"stackAlerts": {
"read": true,
"all": true
'authorized_consumers':
{
'alerts': { 'read': true, 'all': true },
'stackAlerts': { 'read': true, 'all': true },
'slo': { 'read': true, 'all': true },
'ml': { 'read': true, 'all': true },
'uptime': { 'read': true, 'all': true },
'infrastructure': { 'read': true, 'all': true },
'logs': { 'read': true, 'all': true },
'monitoring': { 'read': true, 'all': true },
'siem': { 'read': true, 'all': true },
'apm': { 'read': true, 'all': true },
'discover': { 'read': true, 'all': true },
},
"slo": {
"read": true,
"all": true
},
"ml": {
"read": true,
"all": true
},
"uptime": {
"read": true,
"all": true
},
"infrastructure": {
"read": true,
"all": true
},
"logs": {
"read": true,
"all": true
},
"monitoring": {
"read": true,
"all": true
},
"siem": {
"read": true,
"all": true
},
"apm": {
"read": true,
"all": true
},
"discover": {
"read": true,
"all": true
}
},
"does_set_recovery_context": true,
"has_alerts_mappings": false,
"has_fields_for_a_a_d": false
}
]
'does_set_recovery_context': true,
'has_alerts_mappings': false,
'has_fields_for_a_a_d': false,
},
]

View file

@ -95,10 +95,10 @@ get:
type: string
description: Indicates whether new fields are added dynamically.
enum:
- "false"
- "runtime"
- "strict"
- "true"
- 'false'
- 'runtime'
- 'strict'
- 'true'
isSpaceAware:
type: boolean
description: >

View file

@ -96,7 +96,6 @@ describe('aggregate()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],

View file

@ -110,7 +110,6 @@ describe('find()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -531,7 +530,6 @@ describe('find()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -551,7 +549,6 @@ describe('find()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -794,7 +791,6 @@ describe('find()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -814,7 +810,6 @@ describe('find()', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],

View file

@ -85,7 +85,6 @@ const listedTypes = new Map<string, RegistryRuleType>([
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],

View file

@ -51,7 +51,6 @@ const ruleTypes: RegistryAlertTypeWithAuth[] = [
enabledInLicense: true,
defaultScheduleInterval: '10m',
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
];

View file

@ -63,12 +63,14 @@ describe('ruleTypesRoute', () => {
defaultScheduleInterval: '10m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
const expectedResult: Array<
AsApiContract<Omit<RegistryAlertTypeWithAuth, 'validLegacyConsumers' | 'solution'>>
AsApiContract<Omit<RegistryAlertTypeWithAuth, 'validLegacyConsumers' | 'solution'>> & {
fieldsForAAD: [];
has_fields_for_a_a_d: boolean;
}
> = [
{
id: '1',
@ -94,8 +96,9 @@ describe('ruleTypesRoute', () => {
category: 'test',
producer: 'test',
enabled_in_license: true,
fieldsForAAD: [],
has_alerts_mappings: true,
has_fields_for_a_a_d: false,
has_fields_for_a_a_d: true,
},
];
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
@ -122,8 +125,9 @@ describe('ruleTypesRoute', () => {
"default_schedule_interval": "10m",
"does_set_recovery_context": false,
"enabled_in_license": true,
"fieldsForAAD": Array [],
"has_alerts_mappings": true,
"has_fields_for_a_a_d": false,
"has_fields_for_a_a_d": true,
"id": "1",
"is_exportable": true,
"minimum_license_required": "basic",
@ -180,7 +184,6 @@ describe('ruleTypesRoute', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
@ -238,7 +241,6 @@ describe('ruleTypesRoute', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -27,9 +27,9 @@ export const transformRuleTypesResponse = (
? { does_set_recovery_context: ruleType.doesSetRecoveryContext }
: {}),
enabled_in_license: ruleType.enabledInLicense,
...(ruleType.fieldsForAAD ? { fieldsForAAD: ruleType.fieldsForAAD } : {}),
fieldsForAAD: [],
has_alerts_mappings: ruleType.hasAlertsMappings,
has_fields_for_a_a_d: ruleType.hasFieldsForAAD,
has_fields_for_a_a_d: true,
id: ruleType.id,
is_exportable: ruleType.isExportable,
minimum_license_required: ruleType.minimumLicenseRequired,

View file

@ -63,7 +63,6 @@ describe('internalRuleTypesRoute', () => {
defaultScheduleInterval: '10m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
@ -96,7 +95,6 @@ describe('internalRuleTypesRoute', () => {
solution: 'stack',
enabled_in_license: true,
has_alerts_mappings: true,
has_fields_for_a_a_d: false,
},
];
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
@ -124,7 +122,6 @@ describe('internalRuleTypesRoute', () => {
"does_set_recovery_context": false,
"enabled_in_license": true,
"has_alerts_mappings": true,
"has_fields_for_a_a_d": false,
"id": "1",
"is_exportable": true,
"minimum_license_required": "basic",
@ -182,7 +179,6 @@ describe('internalRuleTypesRoute', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
@ -240,7 +236,6 @@ describe('internalRuleTypesRoute', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -27,9 +27,7 @@ export const transformRuleTypesInternalResponse = (
? { does_set_recovery_context: ruleType.doesSetRecoveryContext }
: {}),
enabled_in_license: ruleType.enabledInLicense,
...(ruleType.fieldsForAAD ? { fieldsForAAD: ruleType.fieldsForAAD } : {}),
has_alerts_mappings: ruleType.hasAlertsMappings,
has_fields_for_a_a_d: ruleType.hasFieldsForAAD,
id: ruleType.id,
is_exportable: ruleType.isExportable,
minimum_license_required: ruleType.minimumLicenseRequired,

View file

@ -947,9 +947,7 @@ describe('Create Lifecycle', () => {
"defaultScheduleInterval": undefined,
"doesSetRecoveryContext": false,
"enabledInLicense": false,
"fieldsForAAD": undefined,
"hasAlertsMappings": true,
"hasFieldsForAAD": false,
"id": "test",
"isExportable": true,
"minimumLicenseRequired": "basic",

View file

@ -70,12 +70,10 @@ export interface RegistryRuleType
| 'ruleTaskTimeout'
| 'defaultScheduleInterval'
| 'doesSetRecoveryContext'
| 'fieldsForAAD'
| 'alerts'
> {
id: string;
enabledInLicense: boolean;
hasFieldsForAAD: boolean;
hasAlertsMappings: boolean;
validLegacyConsumers: string[];
}
@ -405,8 +403,6 @@ export class RuleTypeRegistry {
_ruleType.name,
_ruleType.minimumLicenseRequired
).isValid,
fieldsForAAD: _ruleType.fieldsForAAD,
hasFieldsForAAD: Boolean(_ruleType.fieldsForAAD),
hasAlertsMappings: !!_ruleType.alerts,
...(_ruleType.alerts ? { alerts: _ruleType.alerts } : {}),
validLegacyConsumers: _ruleType.validLegacyConsumers,

View file

@ -84,7 +84,6 @@ describe('listRuleTypes', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
};
@ -102,7 +101,6 @@ describe('listRuleTypes', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
};
@ -155,7 +153,6 @@ describe('listRuleTypes', () => {
"defaultActionGroupId": "default",
"enabledInLicense": true,
"hasAlertsMappings": false,
"hasFieldsForAAD": false,
"id": "myAppAlertType",
"isExportable": true,
"minimumLicenseRequired": "basic",
@ -189,7 +186,6 @@ describe('listRuleTypes', () => {
"defaultActionGroupId": "default",
"enabledInLicense": true,
"hasAlertsMappings": false,
"hasFieldsForAAD": false,
"id": "alertingAlertType",
"isExportable": true,
"minimumLicenseRequired": "basic",
@ -240,7 +236,6 @@ describe('listRuleTypes', () => {
"defaultActionGroupId": "default",
"enabledInLicense": true,
"hasAlertsMappings": false,
"hasFieldsForAAD": false,
"id": "myAppAlertType",
"isExportable": true,
"minimumLicenseRequired": "basic",
@ -275,7 +270,6 @@ describe('listRuleTypes', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -294,7 +288,6 @@ describe('listRuleTypes', () => {
solution: 'stack',
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
],
@ -334,7 +327,6 @@ describe('listRuleTypes', () => {
"defaultActionGroupId": "default",
"enabledInLicense": true,
"hasAlertsMappings": false,
"hasFieldsForAAD": false,
"id": "myType",
"isExportable": true,
"minimumLicenseRequired": "basic",

View file

@ -335,7 +335,6 @@ export interface RuleType<
*/
autoRecoverAlerts?: boolean;
getViewInAppRelativeUrl?: GetViewInAppRelativeUrlFn<Params>;
fieldsForAAD?: string[];
}
export type UntypedRuleType = RuleType<
RuleTypeParams,

View file

@ -25,7 +25,6 @@ const createAlertsClientMock = () => {
ensureAllAlertsAuthorizedRead: jest.fn(),
removeCaseIdFromAlerts: jest.fn(),
removeCaseIdsFromAllAlerts: jest.fn(),
getAADFields: jest.fn(),
};
return mocked;
};

View file

@ -22,7 +22,6 @@ import {
ALERT_STATUS_ACTIVE,
ALERT_CASE_IDS,
MAX_CASES_PER_ALERT,
isSiemRuleType,
} from '@kbn/rule-data-utils';
import type {
@ -173,8 +172,6 @@ export class AlertsClient {
private readonly esClient: ElasticsearchClient;
private readonly spaceId: string | undefined;
private readonly ruleDataService: IRuleDataService;
private readonly getRuleType: RuleTypeRegistry['get'];
private readonly getRuleList: RuleTypeRegistry['list'];
private getAlertIndicesAlias!: AlertingServerStart['getAlertIndicesAlias'];
constructor(options: ConstructorOptions) {
@ -186,8 +183,6 @@ export class AlertsClient {
// Otherwise, if space is enabled and not specified, it is "default"
this.spaceId = this.authorization.getSpaceId();
this.ruleDataService = options.ruleDataService;
this.getRuleType = options.getRuleType;
this.getRuleList = options.getRuleList;
this.getAlertIndicesAlias = options.getAlertIndicesAlias;
}
@ -1209,29 +1204,24 @@ export class AlertsClient {
indices,
metaFields,
allowNoIndex,
includeEmptyFields,
indexFilter,
}: {
ruleTypeIds: string[];
indices: string[];
metaFields: string[];
allowNoIndex: boolean;
includeEmptyFields: boolean;
indexFilter?: estypes.QueryDslQueryContainer;
}): Promise<{ browserFields: BrowserFields; fields: FieldDescriptor[] }> {
const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(this.esClient);
const ruleTypeList = this.getRuleList();
const fieldsForAAD = new Set<string>();
for (const rule of ruleTypeList.values()) {
if (ruleTypeIds.includes(rule.id) && rule.hasFieldsForAAD) {
(rule.fieldsForAAD ?? []).forEach((f) => {
fieldsForAAD.add(f);
});
}
}
const { fields } = await indexPatternsFetcherAsInternalUser.getFieldsForWildcard({
pattern: indices,
metaFields,
fieldCapsOptions: { allow_no_indices: allowNoIndex },
fields: [...fieldsForAAD, 'kibana.*'],
includeEmptyFields,
indexFilter,
});
return {
@ -1239,22 +1229,4 @@ export class AlertsClient {
fields,
};
}
public async getAADFields({ ruleTypeId }: { ruleTypeId: string }) {
const { fieldsForAAD = [] } = this.getRuleType(ruleTypeId);
if (isSiemRuleType(ruleTypeId)) {
throw Boom.badRequest(`Security solution rule type is not supported`);
}
const indices = await this.getAuthorizedAlertsIndices([ruleTypeId]);
const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(this.esClient);
const { fields = [] } = await indexPatternsFetcherAsInternalUser.getFieldsForWildcard({
pattern: indices ?? [],
metaFields: ['_id', '_index'],
fieldCapsOptions: { allow_no_indices: true },
fields: [...fieldsForAAD, 'kibana.*'],
});
return fields;
}
}

View file

@ -1,49 +0,0 @@
/*
* 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 type { ConstructorOptions } from '../alerts_client';
import { AlertsClient } from '../alerts_client';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
import { alertingAuthorizationMock } from '@kbn/alerting-plugin/server/authorization/alerting_authorization.mock';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock';
const alertingAuthMock = alertingAuthorizationMock.create();
const esClientMock = elasticsearchClientMock.createElasticsearchClient();
const auditLogger = auditLoggerMock.create();
const getRuleTypeMock = jest.fn();
const alertsClientParams: jest.Mocked<ConstructorOptions> = {
logger: loggingSystemMock.create().get(),
authorization: alertingAuthMock,
esClient: esClientMock,
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: getRuleTypeMock,
getRuleList: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const DEFAULT_SPACE = 'test_default_space_id';
beforeEach(() => {
jest.resetAllMocks();
alertingAuthMock.getSpaceId.mockImplementation(() => DEFAULT_SPACE);
});
describe('getAADFields()', () => {
test('should throw an error when a rule type belong to security solution', async () => {
getRuleTypeMock.mockImplementation(() => ({
fieldsForAAD: [],
}));
const alertsClient = new AlertsClient(alertsClientParams);
await expect(
alertsClient.getAADFields({ ruleTypeId: 'siem.esqlRule' })
).rejects.toThrowErrorMatchingInlineSnapshot(`"Security solution rule type is not supported"`);
});
});

View file

@ -47,13 +47,6 @@ export const getO11yBrowserFields = () =>
query: { ruleTypeIds: ['apm.anomaly', 'logs.alert.document.count'] },
});
export const getMetricThresholdAADFields = () =>
requestMock.create({
method: 'get',
path: `${BASE_RAC_ALERTS_API_PATH}/aad_fields`,
query: { ruleTypeId: 'metrics.alert.threshold' },
});
export const getAlertsGroupAggregationsRequest = () =>
requestMock.create({
method: 'post',

View file

@ -1,56 +0,0 @@
/*
* 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 { getAADFieldsByRuleType } from './get_aad_fields_by_rule_type';
import { requestContextMock } from './__mocks__/request_context';
import { getMetricThresholdAADFields } from './__mocks__/request_responses';
import { serverMock } from './__mocks__/server';
describe('getAADFieldsByRuleType', () => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
beforeEach(async () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
});
describe('when racClient returns o11y indices', () => {
beforeEach(() => {
clients.rac.getAADFields.mockResolvedValue([
{
name: '_id',
type: 'string',
searchable: false,
aggregatable: false,
readFromDocValues: false,
metadata_field: true,
esTypes: [],
},
]);
getAADFieldsByRuleType(server.router);
});
test('route registered', async () => {
const response = await server.inject(getMetricThresholdAADFields(), context);
expect(response.status).toEqual(200);
});
test('returns error status if rac client "getAADFields" fails', async () => {
clients.rac.getAADFields.mockRejectedValue(new Error('Rule type not registered'));
const response = await server.inject(getMetricThresholdAADFields(), context);
expect(response.status).toEqual(500);
expect(response.body).toEqual({
attributes: { success: false },
message: 'Rule type not registered',
});
});
});
});

View file

@ -1,71 +0,0 @@
/*
* 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 type { IRouter } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import * as t from 'io-ts';
import type { RacRequestHandlerContext } from '../types';
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { buildRouteValidation } from './utils/route_validation';
export const getAADFieldsByRuleType = (router: IRouter<RacRequestHandlerContext>) => {
router.get(
{
path: `${BASE_RAC_ALERTS_API_PATH}/aad_fields`,
validate: {
query: buildRouteValidation(
t.exact(
t.type({
ruleTypeId: t.string,
})
)
),
},
security: {
authz: {
requiredPrivileges: ['rac'],
},
},
options: {
access: 'internal',
},
},
async (context, request, response) => {
try {
const racContext = await context.rac;
const alertsClient = await racContext.getAlertsClient();
const { ruleTypeId } = request.query;
const aadFields = await alertsClient.getAADFields({ ruleTypeId });
return response.ok({
body: aadFields,
});
} catch (error) {
const formatedError = transformError(error);
const contentType = {
'content-type': 'application/json',
};
const defaultedHeaders = {
...contentType,
};
return response.customError({
headers: defaultedHeaders,
statusCode: formatedError.statusCode,
body: {
message: formatedError.message,
attributes: {
success: false,
},
},
});
}
}
);
};

View file

@ -60,11 +60,22 @@ export const getBrowserFieldsByFeatureId = (router: IRouter<RacRequestHandlerCon
});
}
// Limiting the search to fetch browser fields from the last 90 days
const indexFilter = {
range: {
'@timestamp': {
gte: 'now-90d',
},
},
};
const fields = await alertsClient.getBrowserFields({
indices: o11yIndices,
ruleTypeIds: onlyO11yRuleTypeIds,
metaFields: ['_id', '_index'],
allowNoIndex: true,
includeEmptyFields: false,
indexFilter,
});
return response.ok({

View file

@ -15,7 +15,6 @@ import { bulkUpdateAlertsRoute } from './bulk_update_alerts';
import { findAlertsByQueryRoute } from './find';
import { getBrowserFieldsByFeatureId } from './get_browser_fields_by_rule_type_ids';
import { getAlertSummaryRoute } from './get_alert_summary';
import { getAADFieldsByRuleType } from './get_aad_fields_by_rule_type';
export function defineRoutes(router: IRouter<RacRequestHandlerContext>) {
getAlertByIdRoute(router);
@ -26,5 +25,4 @@ export function defineRoutes(router: IRouter<RacRequestHandlerContext>) {
getAlertsGroupAggregations(router);
getBrowserFieldsByFeatureId(router);
getAlertSummaryRoute(router);
getAADFieldsByRuleType(router);
}

View file

@ -12,11 +12,11 @@ import { useEffect, useMemo, useState } from 'react';
import { EcsFlat } from '@elastic/ecs';
import {
fetchRuleTypeAadTemplateFields,
fetchRuleTypeAlertFields,
getDescription,
} from '@kbn/alerts-ui-shared/src/common/apis/fetch_rule_type_aad_template_fields';
} from '@kbn/alerts-ui-shared/src/common/apis/fetch_rule_type_alert_fields';
export function useRuleTypeAadTemplateFields(
export function useRuleTypeAlertFields(
http: HttpStart,
ruleTypeId: string | undefined,
enabled: boolean
@ -28,7 +28,7 @@ export function useRuleTypeAadTemplateFields(
useEffect(() => {
if (enabled && data.length === 0 && ruleTypeId) {
setIsLoading(true);
fetchRuleTypeAadTemplateFields({ http, ruleTypeId }).then((res) => {
fetchRuleTypeAlertFields({ http, ruleTypeId }).then((res) => {
setData(res);
setIsLoading(false);
});

View file

@ -77,7 +77,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: '.index-threshold',
name: 'Index threshold',
category: 'management',
@ -154,7 +153,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: '.geo-containment',
name: 'Tracking containment',
category: 'management',
@ -231,7 +229,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: '.es-query',
name: 'Elasticsearch query',
category: 'management',
@ -308,7 +305,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'transform_health',
name: 'Transform health',
category: 'management',
@ -385,7 +381,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.ml.anomaly_detection_alert',
name: 'Anomaly detection',
category: 'management',
@ -462,7 +457,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'xpack.ml.anomaly_detection_jobs_health',
name: 'Anomaly detection jobs health',
category: 'management',
@ -551,12 +545,10 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: true,
id: 'slo.rules.burnRate',
name: 'SLO burn rate',
category: 'observability',
producer: 'slo',
fieldsForAAD: ['slo.id', 'slo.revision', 'slo.instanceId'],
isExportable: true,
},
],
@ -629,7 +621,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'xpack.uptime.alerts.tls',
name: 'Uptime TLS (Legacy)',
category: 'observability',
@ -706,7 +697,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.uptime.alerts.tlsCertificate',
name: 'Uptime TLS',
category: 'observability',
@ -783,7 +773,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.uptime.alerts.monitorStatus',
name: 'Uptime monitor status',
category: 'observability',
@ -860,7 +849,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.uptime.alerts.durationAnomaly',
name: 'Uptime Duration Anomaly',
category: 'observability',
@ -937,7 +925,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.synthetics.alerts.monitorStatus',
name: 'Synthetics monitor status',
category: 'observability',
@ -1014,7 +1001,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'xpack.synthetics.alerts.tls',
name: 'Synthetics TLS certificate',
category: 'observability',
@ -1099,12 +1085,10 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: true,
id: 'metrics.alert.threshold',
name: 'Metric threshold',
category: 'observability',
producer: 'infrastructure',
fieldsForAAD: ['cloud.*', 'host.*', 'orchestrator.*', 'container.*', 'labels.*', 'tags'],
isExportable: true,
},
],
@ -1181,12 +1165,10 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: true,
id: 'metrics.alert.inventory.threshold',
name: 'Inventory',
category: 'observability',
producer: 'infrastructure',
fieldsForAAD: ['cloud.*', 'host.*', 'orchestrator.*', 'container.*', 'labels.*', 'tags'],
isExportable: true,
},
],
@ -1263,12 +1245,10 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: true,
id: 'observability.rules.custom_threshold',
name: 'Custom threshold (Beta)',
category: 'observability',
producer: 'observability',
fieldsForAAD: ['cloud.*', 'host.*', 'orchestrator.*', 'container.*', 'labels.*', 'tags'],
isExportable: true,
},
],
@ -1341,12 +1321,10 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: true,
hasAlertsMappings: true,
hasFieldsForAAD: true,
id: 'logs.alert.document.count',
name: 'Log threshold',
category: 'observability',
producer: 'logs',
fieldsForAAD: ['cloud.*', 'host.*', 'orchestrator.*', 'container.*', 'labels.*', 'tags'],
isExportable: true,
},
],
@ -1419,7 +1397,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_license_expiration',
name: 'License expiration',
category: 'management',
@ -1496,7 +1473,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_cluster_health',
name: 'Cluster health',
category: 'management',
@ -1573,7 +1549,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_cpu_usage',
name: 'CPU Usage',
category: 'management',
@ -1650,7 +1625,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_disk_usage',
name: 'Disk Usage',
category: 'management',
@ -1727,7 +1701,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_nodes_changed',
name: 'Nodes changed',
category: 'management',
@ -1804,7 +1777,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_elasticsearch_version_mismatch',
name: 'Elasticsearch version mismatch',
category: 'management',
@ -1881,7 +1853,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_kibana_version_mismatch',
name: 'Kibana version mismatch',
category: 'management',
@ -1958,7 +1929,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_logstash_version_mismatch',
name: 'Logstash version mismatch',
category: 'management',
@ -2035,7 +2005,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_jvm_memory_usage',
name: 'Memory Usage (JVM)',
category: 'management',
@ -2112,7 +2081,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_missing_monitoring_data',
name: 'Missing monitoring data',
category: 'management',
@ -2189,7 +2157,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_thread_pool_search_rejections',
name: 'Thread pool search rejections',
category: 'management',
@ -2266,7 +2233,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_alert_thread_pool_write_rejections',
name: 'Thread pool write rejections',
category: 'management',
@ -2343,7 +2309,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_ccr_read_exceptions',
name: 'CCR read exceptions',
category: 'management',
@ -2420,7 +2385,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'monitoring_shard_size',
name: 'Shard size',
category: 'management',
@ -2497,7 +2461,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'apm.error_rate',
name: 'Error count threshold',
category: 'observability',
@ -2574,7 +2537,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'apm.transaction_error_rate',
name: 'Failed transaction rate threshold',
category: 'observability',
@ -2651,7 +2613,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'apm.transaction_duration',
name: 'Latency threshold',
category: 'observability',
@ -2728,7 +2689,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'apm.anomaly',
name: 'APM Anomaly',
category: 'observability',
@ -2805,7 +2765,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: false,
hasFieldsForAAD: false,
id: 'siem.notifications',
name: 'Security Solution notification (Legacy)',
category: 'securitySolution',
@ -2882,7 +2841,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.esqlRule',
name: 'ES|QL Rule',
category: 'securitySolution',
@ -2959,7 +2917,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.eqlRule',
name: 'Event Correlation Rule',
category: 'securitySolution',
@ -3036,7 +2993,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '1h',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.indicatorRule',
name: 'Indicator Match Rule',
category: 'securitySolution',
@ -3113,7 +3069,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.mlRule',
name: 'Machine Learning Rule',
category: 'securitySolution',
@ -3190,7 +3145,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.queryRule',
name: 'Custom Query Rule',
category: 'securitySolution',
@ -3267,7 +3221,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.savedQueryRule',
name: 'Saved Query Rule',
category: 'securitySolution',
@ -3344,7 +3297,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.thresholdRule',
name: 'Threshold Rule',
category: 'securitySolution',
@ -3421,7 +3373,6 @@ export const ruleTypesIndex = new Map([
ruleTaskTimeout: '5m',
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
id: 'siem.newTermsRule',
name: 'New Terms Rule',
category: 'securitySolution',

View file

@ -10,6 +10,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
import { EuiAccordion } from '@elastic/eui';
import { coreMock } from '@kbn/core/public/mocks';
import { act } from 'react-dom/test-utils';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import { ValidationResult, GenericValidationResult, RuleUiAction } from '../../../types';
import ActionForm from './action_form';
@ -340,67 +341,69 @@ describe('action_form', () => {
const defaultActionMessage = 'Alert [{{context.metadata.name}}] has exceeded the threshold';
const wrapper = mountWithIntl(
<ActionForm
actions={initialAlert.actions}
messageVariables={{
params: [
{ name: 'testVar1', description: 'test var1' },
{ name: 'testVar2', description: 'test var2' },
],
state: [],
context: [{ name: 'contextVar', description: 'context var1' }],
}}
featureId="alerting"
producerId="alerting"
defaultActionGroupId={'default'}
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) => {
const recoveryActionGroupId = customRecoveredActionGroup
? customRecoveredActionGroup
: 'recovered';
return isActionGroupDisabledForActionTypeId(
actionGroupId === recoveryActionGroupId ? RecoveredActionGroup.id : actionGroupId,
actionTypeId
);
}}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;
}}
actionGroups={[
{ id: 'default', name: 'Default', defaultActionMessage },
{
id: customRecoveredActionGroup ? customRecoveredActionGroup : 'recovered',
name: customRecoveredActionGroup ? 'I feel better' : 'Recovered',
},
]}
setActionGroupIdByIndex={(group: string, index: number) => {
initialAlert.actions[index].group = group;
}}
setActions={(_updatedActions: RuleUiAction[]) => {}}
setActionParamsProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionFrequencyProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = {
...initialAlert.actions[index],
frequency: {
...initialAlert.actions[index].frequency!,
[key]: value,
<QueryClientProvider client={new QueryClient()}>
<ActionForm
actions={initialAlert.actions}
messageVariables={{
params: [
{ name: 'testVar1', description: 'test var1' },
{ name: 'testVar2', description: 'test var2' },
],
state: [],
context: [{ name: 'contextVar', description: 'context var1' }],
}}
featureId="alerting"
producerId="alerting"
defaultActionGroupId={'default'}
isActionGroupDisabledForActionType={(actionGroupId: string, actionTypeId: string) => {
const recoveryActionGroupId = customRecoveredActionGroup
? customRecoveredActionGroup
: 'recovered';
return isActionGroupDisabledForActionTypeId(
actionGroupId === recoveryActionGroupId ? RecoveredActionGroup.id : actionGroupId,
actionTypeId
);
}}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;
}}
actionGroups={[
{ id: 'default', name: 'Default', defaultActionMessage },
{
id: customRecoveredActionGroup ? customRecoveredActionGroup : 'recovered',
name: customRecoveredActionGroup ? 'I feel better' : 'Recovered',
},
})
}
setActionAlertsFilterProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = {
...initialAlert.actions[index],
alertsFilter: {
...initialAlert.actions[index].alertsFilter,
[key]: value,
},
})
}
actionTypeRegistry={actionTypeRegistry}
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
ruleTypeId=".es-query"
/>
]}
setActionGroupIdByIndex={(group: string, index: number) => {
initialAlert.actions[index].group = group;
}}
setActions={(_updatedActions: RuleUiAction[]) => {}}
setActionParamsProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
}
setActionFrequencyProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = {
...initialAlert.actions[index],
frequency: {
...initialAlert.actions[index].frequency!,
[key]: value,
},
})
}
setActionAlertsFilterProperty={(key: string, value: any, index: number) =>
(initialAlert.actions[index] = {
...initialAlert.actions[index],
alertsFilter: {
...initialAlert.actions[index].alertsFilter,
[key]: value,
},
})
}
actionTypeRegistry={actionTypeRegistry}
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
ruleTypeId=".es-query"
/>
</QueryClientProvider>
);
// Wait for active space to resolve before requesting the component to update

View file

@ -85,7 +85,6 @@ export interface ActionAccordionFormProps {
minimumThrottleInterval?: [number | undefined, string];
notifyWhenSelectOptions?: NotifyWhenSelectOptions[];
defaultRuleFrequency?: RuleActionFrequency;
hasFieldsForAAD?: boolean;
disableErrorMessages?: boolean;
}
@ -123,7 +122,6 @@ export const ActionForm = ({
defaultRuleFrequency = DEFAULT_FREQUENCY,
ruleTypeId,
producerId,
hasFieldsForAAD,
disableErrorMessages,
}: ActionAccordionFormProps) => {
const {
@ -555,7 +553,6 @@ export const ActionForm = ({
featureId={featureId}
producerId={producerId}
ruleTypeId={ruleTypeId}
hasFieldsForAAD={hasFieldsForAAD}
disableErrorMessages={disableErrorMessages}
/>
);

View file

@ -6,6 +6,7 @@
*/
import * as React from 'react';
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ActionTypeForm } from './action_type_form';
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import {
@ -21,7 +22,11 @@ import { EuiFieldText } from '@elastic/eui';
import { I18nProvider, __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { render, waitFor, screen } from '@testing-library/react';
import { DEFAULT_FREQUENCY } from '../../../common/constants';
import { RuleNotifyWhen, SanitizedRuleAction } from '@kbn/alerting-plugin/common';
import {
ALERTING_FEATURE_ID,
RuleNotifyWhen,
SanitizedRuleAction,
} from '@kbn/alerting-plugin/common';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { transformActionVariables } from '@kbn/alerts-ui-shared/src/action_variables/transforms';
@ -48,10 +53,37 @@ const CUSTOM_NOTIFY_WHEN_OPTIONS: NotifyWhenSelectOptions[] = [
},
];
const mockedRuleTypeIndex = new Map(
Object.entries({
test_rule_type: {
enabledInLicense: true,
id: 'test_rule_type',
name: 'test rule',
actionGroups: [{ id: 'default', name: 'Default' }],
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
actionVariables: { context: [], state: [] },
defaultActionGroupId: 'default',
producer: ALERTING_FEATURE_ID,
minimumLicenseRequired: 'basic',
authorizedConsumers: {
[ALERTING_FEATURE_ID]: { read: true, all: false },
},
ruleTaskTimeout: '1m',
},
})
);
const actionTypeRegistry = actionTypeRegistryMock.create();
jest.mock('../../../common/lib/kibana');
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions', () => ({
useGetRuleTypesPermissions: jest.fn(),
}));
const { useGetRuleTypesPermissions } = jest.requireMock(
'@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions'
);
jest.mock('@kbn/alerts-ui-shared/src/action_variables/transforms', () => {
const original = jest.requireActual('@kbn/alerts-ui-shared/src/action_variables/transforms');
return {
@ -70,8 +102,8 @@ jest.mock('../../../common/get_experimental_features', () => ({
},
}));
jest.mock('../../hooks/use_rule_aad_template_fields', () => ({
useRuleTypeAadTemplateFields: () => ({
jest.mock('../../hooks/use_rule_alert_fields', () => ({
useRuleTypeAlertFields: () => ({
isLoading: false,
fields: [],
}),
@ -82,6 +114,14 @@ describe('action_type_form', () => {
jest.clearAllMocks();
});
useGetRuleTypesPermissions.mockReturnValue({
ruleTypesState: {
isLoading: false,
isInitialLoading: false,
data: mockedRuleTypeIndex,
},
});
const mockedActionParamsFields = React.lazy(async () => ({
default() {
return (
@ -748,27 +788,29 @@ function getActionTypeForm({
};
return (
<ActionTypeForm
actionConnector={actionConnector ?? actionConnectorDefault}
actionItem={actionItem ?? actionItemDefault}
connectors={connectors ?? connectorsDefault}
onAddConnector={onAddConnector ?? jest.fn()}
onDeleteAction={onDeleteAction ?? jest.fn()}
onConnectorSelected={onConnectorSelected ?? jest.fn()}
defaultActionGroupId={defaultActionGroupId ?? 'default'}
setActionParamsProperty={setActionParamsProperty ?? jest.fn()}
setActionFrequencyProperty={setActionFrequencyProperty ?? jest.fn()}
setActionAlertsFilterProperty={setActionAlertsFilterProperty ?? jest.fn()}
index={index ?? 1}
actionTypesIndex={actionTypeIndex ?? actionTypeIndexDefault}
actionTypeRegistry={actionTypeRegistry}
hasAlertsMappings={hasAlertsMappings}
messageVariables={messageVariables}
summaryMessageVariables={summaryMessageVariables}
notifyWhenSelectOptions={notifyWhenSelectOptions}
producerId={producerId}
featureId={featureId}
ruleTypeId={ruleTypeId}
/>
<QueryClientProvider client={new QueryClient()}>
<ActionTypeForm
actionConnector={actionConnector ?? actionConnectorDefault}
actionItem={actionItem ?? actionItemDefault}
connectors={connectors ?? connectorsDefault}
onAddConnector={onAddConnector ?? jest.fn()}
onDeleteAction={onDeleteAction ?? jest.fn()}
onConnectorSelected={onConnectorSelected ?? jest.fn()}
defaultActionGroupId={defaultActionGroupId ?? 'default'}
setActionParamsProperty={setActionParamsProperty ?? jest.fn()}
setActionFrequencyProperty={setActionFrequencyProperty ?? jest.fn()}
setActionAlertsFilterProperty={setActionAlertsFilterProperty ?? jest.fn()}
index={index ?? 1}
actionTypesIndex={actionTypeIndex ?? actionTypeIndexDefault}
actionTypeRegistry={actionTypeRegistry}
hasAlertsMappings={hasAlertsMappings}
messageVariables={messageVariables}
summaryMessageVariables={summaryMessageVariables}
notifyWhenSelectOptions={notifyWhenSelectOptions}
producerId={producerId}
featureId={featureId}
ruleTypeId={ruleTypeId}
/>
</QueryClientProvider>
);
}

View file

@ -52,6 +52,7 @@ import {
} from '@kbn/response-ops-rule-form';
import { checkActionFormActionTypeEnabled, transformActionVariables } from '@kbn/alerts-ui-shared';
import { ActionGroupWithMessageVariables } from '@kbn/triggers-actions-ui-types';
import { useGetRuleTypesPermissions } from '@kbn/alerts-ui-shared/src/common/hooks';
import { TECH_PREVIEW_DESCRIPTION, TECH_PREVIEW_LABEL } from '../translations';
import { getIsExperimentalFeatureEnabled } from '../../../common/get_experimental_features';
import {
@ -70,7 +71,7 @@ import { useKibana } from '../../../common/lib/kibana';
import { ConnectorsSelection } from './connectors_selection';
import { validateParamsForWarnings } from '../../lib/validate_params_for_warnings';
import { validateActionFilterQuery } from '../../lib/value_validators';
import { useRuleTypeAadTemplateFields } from '../../hooks/use_rule_aad_template_fields';
import { useRuleTypeAlertFields } from '../../hooks/use_rule_alert_fields';
export type ActionTypeFormProps = {
actionItem: RuleAction;
@ -99,7 +100,6 @@ export type ActionTypeFormProps = {
featureId: string;
producerId: string;
ruleTypeId?: string;
hasFieldsForAAD?: boolean;
disableErrorMessages?: boolean;
} & Pick<
ActionAccordionFormProps,
@ -150,7 +150,6 @@ export const ActionTypeForm = ({
producerId,
featureId,
ruleTypeId,
hasFieldsForAAD,
disableErrorMessages,
}: ActionTypeFormProps) => {
const {
@ -190,22 +189,23 @@ export const ActionTypeForm = ({
const isSummaryAction = actionItem.frequency?.summary;
const [useAadTemplateFields, setUseAadTemplateField] = useState(
const [useAlertTemplateFields, setUseAlertTemplateFields] = useState(
actionItem?.useAlertDataForTemplate ?? false
);
const [storedActionParamsForAadToggle, setStoredActionParamsForAadToggle] = useState<
Record<string, SavedObjectAttribute>
>({});
const [storedActionParamsForAlertFieldsToggle, setStoredActionParamsForAlertFieldsToggle] =
useState<Record<string, SavedObjectAttribute>>({});
const { fields: aadTemplateFields } = useRuleTypeAadTemplateFields(
const { fields: alertFields } = useRuleTypeAlertFields(http, ruleTypeId, useAlertTemplateFields);
const { ruleTypesState } = useGetRuleTypesPermissions({
http,
ruleTypeId,
useAadTemplateFields
);
toasts: notifications.toasts,
filteredRuleTypes: [],
});
const templateFields = useMemo(
() => (useAadTemplateFields ? aadTemplateFields : availableActionVariables),
[aadTemplateFields, availableActionVariables, useAadTemplateFields]
() => (useAlertTemplateFields ? alertFields : availableActionVariables),
[alertFields, availableActionVariables, useAlertTemplateFields]
);
const actAccordionActionFormCss = css`
@ -238,8 +238,8 @@ export const ActionTypeForm = ({
showMustacheAutocompleteSwitch = false;
}
const handleUseAadTemplateFields = useCallback(() => {
setUseAadTemplateField((prevVal) => {
const handleUseAlertTemplateFields = useCallback(() => {
setUseAlertTemplateFields((prevVal) => {
if (setActionUseAlertDataForTemplate) {
setActionUseAlertDataForTemplate(!prevVal, index);
}
@ -247,13 +247,13 @@ export const ActionTypeForm = ({
});
const currentActionParams = { ...actionItem.params };
for (const key of Object.keys(currentActionParams)) {
setActionParamsProperty(key, storedActionParamsForAadToggle[key] ?? '', index);
setActionParamsProperty(key, storedActionParamsForAlertFieldsToggle[key] ?? '', index);
}
setStoredActionParamsForAadToggle(currentActionParams);
setStoredActionParamsForAlertFieldsToggle(currentActionParams);
}, [
setActionUseAlertDataForTemplate,
storedActionParamsForAadToggle,
setStoredActionParamsForAadToggle,
storedActionParamsForAlertFieldsToggle,
setStoredActionParamsForAlertFieldsToggle,
setActionParamsProperty,
actionItem.params,
index,
@ -316,7 +316,7 @@ export const ActionTypeForm = ({
defaultAADParams[key] = paramValue;
}
}
setStoredActionParamsForAadToggle(defaultAADParams);
setStoredActionParamsForAlertFieldsToggle(defaultAADParams);
}
}
})();
@ -334,7 +334,7 @@ export const ActionTypeForm = ({
defaultAADParams[key] = paramValue;
}
}
setStoredActionParamsForAadToggle(defaultAADParams);
setStoredActionParamsForAlertFieldsToggle(defaultAADParams);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -366,10 +366,10 @@ export const ActionTypeForm = ({
}, [actionItem, disableErrorMessages]);
useEffect(() => {
if (isEmpty(storedActionParamsForAadToggle) && actionItem.params.subAction) {
setStoredActionParamsForAadToggle(actionItem.params);
if (isEmpty(storedActionParamsForAlertFieldsToggle) && actionItem.params.subAction) {
setStoredActionParamsForAlertFieldsToggle(actionItem.params);
}
}, [actionItem.params, storedActionParamsForAadToggle]);
}, [actionItem.params, storedActionParamsForAlertFieldsToggle]);
const canSave = hasSaveActionsCapability(capabilities);
@ -444,7 +444,9 @@ export const ActionTypeForm = ({
setActionGroupIdByIndex &&
!actionItem.frequency?.summary;
const showActionAlertsFilter = hasFieldsForAAD || producerId === AlertConsumers.SIEM;
const ruleType = ruleTypeId ? ruleTypesState.data.get(ruleTypeId) : null;
const showActionAlertsFilter = ruleType?.hasAlertsMappings || producerId === AlertConsumers.SIEM;
const accordionContent = checkEnabledResult.isEnabled ? (
<>
@ -559,8 +561,8 @@ export const ActionTypeForm = ({
<EuiFlexItem>
<EuiSwitch
label="Use template fields from alerts index"
checked={useAadTemplateFields}
onChange={handleUseAadTemplateFields}
checked={useAlertTemplateFields}
onChange={handleUseAlertTemplateFields}
data-test-subj="mustacheAutocompleteSwitch"
/>
</EuiFlexItem>

View file

@ -26,8 +26,8 @@ jest.mock('@kbn/alerts-ui-shared/src/action_variables/transforms', () => {
};
});
jest.mock('../../hooks/use_rule_aad_template_fields', () => ({
useRuleTypeAadTemplateFields: () => ({
jest.mock('../../hooks/use_rule_alert_fields', () => ({
useRuleTypeAlertFields: () => ({
isLoading: false,
fields: [],
}),

View file

@ -43,7 +43,7 @@ import {
import { ActionAccordionFormProps } from './action_form';
import { useKibana } from '../../../common/lib/kibana';
import { validateParamsForWarnings } from '../../lib/validate_params_for_warnings';
import { useRuleTypeAadTemplateFields } from '../../hooks/use_rule_aad_template_fields';
import { useRuleTypeAlertFields } from '../../hooks/use_rule_alert_fields';
export type SystemActionTypeFormProps = {
actionItem: RuleSystemAction;
@ -117,7 +117,7 @@ export const SystemActionTypeForm = ({
const [warning, setWarning] = useState<string | null>(null);
const { fields: aadTemplateFields } = useRuleTypeAadTemplateFields(http, ruleTypeId, true);
const { fields: alertFields } = useRuleTypeAlertFields(http, ruleTypeId, true);
const getDefaultParams = useCallback(() => {
const connectorType = actionTypeRegistry.get(actionItem.actionTypeId);
@ -220,7 +220,7 @@ export const SystemActionTypeForm = ({
);
setActionParamsProperty(key, value, i);
}}
messageVariables={aadTemplateFields}
messageVariables={alertFields}
defaultMessage={defaultSummaryMessage}
useDefaultMessage={true}
actionConnector={actionConnector}

View file

@ -300,7 +300,7 @@ describe('rules', () => {
enabled: false,
});
const ruleType = mockRuleType({
hasFieldsForAAD: true,
hasAlertsMappings: true,
});
const ruleSummary = mockRuleSummary();
jest.setSystemTime(fake2MinutesAgo);

View file

@ -112,7 +112,7 @@ export function RuleComponent({
});
const renderRuleAlertList = useCallback(() => {
if (ruleType.hasAlertsMappings || ruleType.hasFieldsForAAD) {
if (ruleType.hasAlertsMappings) {
return (
<AlertsTable
id="rule-detail-alerts-table"
@ -154,7 +154,6 @@ export function RuleComponent({
readOnly,
rule.id,
ruleType.hasAlertsMappings,
ruleType.hasFieldsForAAD,
ruleType.id,
settings,
]);

View file

@ -41,7 +41,6 @@ import {
WARNING_ACTIONS,
} from './inventory_metric_threshold_executor';
import { MetricsRulesTypeAlertDefinition } from '../register_rule_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
const groupActionVariableDescription = i18n.translate(
'xpack.infra.inventory.alerting.groupActionVariableDescription',
@ -125,7 +124,6 @@ export function registerInventoryThresholdRuleType(
],
},
alerts: MetricsRulesTypeAlertDefinition,
fieldsForAAD: O11Y_AAD_FIELDS,
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -14,7 +14,6 @@ import type {
import { observabilityFeatureId, observabilityPaths } from '@kbn/observability-plugin/common';
import { logThresholdParamsSchema } from '@kbn/response-ops-rule-params/log_threshold';
import type { InfraConfig } from '../../../../common/plugin_config_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import { extractReferences, injectReferences } from './log_threshold_references_manager';
import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../../../../common/alerting/logs/log_threshold';
@ -173,7 +172,6 @@ export function registerLogThresholdRuleType(
injectReferences,
},
alerts: LogsRulesTypeAlertDefinition,
fieldsForAAD: O11Y_AAD_FIELDS,
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -43,7 +43,6 @@ import {
NO_DATA_ACTIONS,
} from './metric_threshold_executor';
import { MetricsRulesTypeAlertDefinition } from '../register_rule_types';
import { O11Y_AAD_FIELDS } from '../../../../common/constants';
export function registerMetricThresholdRuleType(
alertingPlugin: AlertingServerSetup,
@ -68,7 +67,6 @@ export function registerMetricThresholdRuleType(
name: i18n.translate('xpack.infra.metrics.alertName', {
defaultMessage: 'Metric threshold',
}),
fieldsForAAD: O11Y_AAD_FIELDS,
validate: {
params: metricThresholdRuleParamsSchema,
},

View file

@ -36,7 +36,7 @@ import {
createCustomThresholdExecutor,
CustomThresholdLocators,
} from './custom_threshold_executor';
import { CUSTOM_THRESHOLD_AAD_FIELDS, FIRED_ACTION, NO_DATA_ACTION } from './constants';
import { FIRED_ACTION, NO_DATA_ACTION } from './constants';
import { ObservabilityConfig } from '../../..';
import { CustomThresholdAlert } from './types';
@ -59,7 +59,6 @@ export function thresholdRuleType(
name: i18n.translate('xpack.observability.threshold.ruleName', {
defaultMessage: 'Custom threshold',
}),
fieldsForAAD: CUSTOM_THRESHOLD_AAD_FIELDS,
validate: {
params: customThresholdParamsSchema,
},

View file

@ -16,7 +16,6 @@ import { AlertsLocatorParams, observabilityPaths } from '@kbn/observability-plug
import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { sloFeatureId } from '@kbn/observability-plugin/common';
import { sloBurnRateParamsSchema } from '@kbn/response-ops-rule-params/slo_burn_rate';
import { SLO_BURN_RATE_AAD_FIELDS } from '../../../../common/field_names/slo';
import { SLO_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
import {
@ -39,7 +38,6 @@ export function sloBurnRateRuleType(
name: i18n.translate('xpack.slo.rules.burnRate.name', {
defaultMessage: 'SLO burn rate',
}),
fieldsForAAD: SLO_BURN_RATE_AAD_FIELDS,
validate: {
params: sloBurnRateParamsSchema,
},

View file

@ -15,7 +15,6 @@ import { syntheticsMonitorStatusRuleParamsSchema } from '@kbn/response-ops-rule-
import { SyntheticsEsClient } from '../../lib';
import { AlertOverviewStatus } from '../../../common/runtime_types/alert_rules/common';
import { StatusRuleExecutorOptions } from './types';
import { syntheticsRuleFieldMap } from '../../../common/rules/synthetics_rule_field_map';
import { SyntheticsPluginsSetupDependencies, SyntheticsServerSetup } from '../../types';
import { StatusRuleExecutor } from './status_rule_executor';
import { MONITOR_STATUS } from '../../../common/constants/synthetics_alerts';
@ -108,7 +107,6 @@ export const registerSyntheticsStatusCheckRule = (
};
},
alerts: SyntheticsRuleTypeAlertDefinition,
fieldsForAAD: Object.keys(syntheticsRuleFieldMap),
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -25,7 +25,6 @@ import {
observabilityPaths,
} from '@kbn/observability-plugin/common';
import { ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils';
import { syntheticsRuleFieldMap } from '../../../common/rules/synthetics_rule_field_map';
import { SyntheticsPluginsSetupDependencies, SyntheticsServerSetup } from '../../types';
import { getCertSummary, getTLSAlertDocument, setTLSRecoveredAlertsContext } from './message_utils';
import { SyntheticsCommonState } from '../../../common/runtime_types/alert_rules/common';
@ -136,7 +135,6 @@ export const registerSyntheticsTLSCheckRule = (
return { state: updateState(ruleState, foundCerts) };
},
alerts: SyntheticsRuleTypeAlertDefinition,
fieldsForAAD: Object.keys(syntheticsRuleFieldMap),
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -27,7 +27,6 @@ import {
} from '@kbn/observability-plugin/common';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { asyncForEach } from '@kbn/std';
import { uptimeRuleFieldMap } from '../../../../common/rules/uptime_rule_field_map';
import { MonitorSummary, UptimeAlertTypeFactory } from './types';
import {
StatusCheckFilters,
@ -545,7 +544,6 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (
return { state: updateState(state, downMonitorsByLocation.length > 0) };
},
alerts: UptimeRuleTypeAlertDefinition,
fieldsForAAD: Object.keys(uptimeRuleFieldMap),
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -22,7 +22,6 @@ import { asyncForEach } from '@kbn/std';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { uptimeTLSRuleParamsSchema } from '@kbn/response-ops-rule-params/uptime_tls';
import { uptimeRuleFieldMap } from '../../../../common/rules/uptime_rule_field_map';
import { formatFilterString } from './status_check';
import { UptimeAlertTypeFactory } from './types';
import {
@ -258,7 +257,6 @@ export const tlsAlertFactory: UptimeAlertTypeFactory<ActionGroupIds> = (
return { state: updateState(state, foundCerts) };
},
alerts: UptimeRuleTypeAlertDefinition,
fieldsForAAD: Object.keys(uptimeRuleFieldMap),
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});

View file

@ -38,8 +38,9 @@ export default function listRuleTypes({ getService }: FtrProviderContext) {
name: 'Recovered',
},
enabled_in_license: true,
has_fields_for_a_a_d: false,
fieldsForAAD: [],
has_alerts_mappings: false,
has_fields_for_a_a_d: true,
rule_task_timeout: '5m',
};
@ -66,8 +67,9 @@ export default function listRuleTypes({ getService }: FtrProviderContext) {
minimum_license_required: 'basic',
is_exportable: true,
enabled_in_license: true,
has_fields_for_a_a_d: false,
fieldsForAAD: [],
has_alerts_mappings: false,
has_fields_for_a_a_d: true,
rule_task_timeout: '5m',
};

View file

@ -38,7 +38,6 @@ export default function listInternalRuleTypes({ getService }: FtrProviderContext
name: 'Recovered',
},
enabled_in_license: true,
has_fields_for_a_a_d: false,
has_alerts_mappings: false,
rule_task_timeout: '5m',
solution: 'stack',
@ -67,13 +66,12 @@ export default function listInternalRuleTypes({ getService }: FtrProviderContext
minimum_license_required: 'basic',
is_exportable: true,
enabled_in_license: true,
has_fields_for_a_a_d: false,
has_alerts_mappings: false,
rule_task_timeout: '5m',
solution: 'stack',
};
describe('_rule_types', () => {
describe('_rule_types_internal', () => {
for (const scenario of UserAtSpaceScenarios) {
const { user, space } = scenario;
describe(scenario.id, () => {

View file

@ -46,8 +46,9 @@ export default function listRuleTypes({ getService }: FtrProviderContext) {
minimum_license_required: 'basic',
is_exportable: true,
enabled_in_license: true,
has_fields_for_a_a_d: false,
fieldsForAAD: [],
has_alerts_mappings: false,
has_fields_for_a_a_d: true,
rule_task_timeout: '5m',
});
expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture');

View file

@ -46,7 +46,6 @@ export default function listRuleTypes({ getService }: FtrProviderContext) {
minimum_license_required: 'basic',
is_exportable: true,
enabled_in_license: true,
has_fields_for_a_a_d: false,
has_alerts_mappings: false,
rule_task_timeout: '5m',
solution: 'stack',

View file

@ -118,9 +118,40 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await rules.common.cancelRuleCreation();
}
const esQueryRule = {
tags: [],
params: {
searchType: 'esQuery',
timeWindowSize: 5,
timeWindowUnit: 'm',
threshold: [1],
thresholdComparator: '>',
size: 100,
esQuery: '{\n "query":{\n "match_all" : {}\n }\n }',
aggType: 'count',
groupBy: 'all',
termSize: 5,
excludeHitsFromPreviousRun: false,
sourceFields: [],
index: ['.kibana_alerting_cases'],
timeField: 'updated_at',
},
schedule: {
interval: '1m',
},
consumer: 'stackAlerts',
name: 'Elasticsearch query rule',
rule_type_id: '.es-query',
actions: [],
alert_delay: {
active: 1,
},
};
describe('create alert', function () {
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
const webhookConnectorName = 'webhook-test';
let esQueryRuleId: string;
before(async () => {
await esArchiver.load(
'src/platform/test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword'
@ -128,6 +159,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await createWebhookConnector(webhookConnectorName);
const { body: createdESRule } = await supertest
.post('/api/alerting/rule')
.set('kbn-xsrf', 'foo')
.send(esQueryRule)
.expect(200);
esQueryRuleId = createdESRule.id;
const version = (await apmSynthtraceKibanaClient.installApmPackage()).version;
apmSynthtraceEsClient = await getApmSynthtraceEsClient({
client: esClient,
@ -168,6 +207,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await esArchiver.unload(
'src/platform/test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword'
);
await supertest.delete(`/api/alerting/rule/${esQueryRuleId}`).set('kbn-xsrf', 'foo');
await deleteConnectorByName(webhookConnectorName);
});

View file

@ -6,6 +6,7 @@
*/
import expect from '@kbn/expect';
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
import { FtrProviderContext } from '../../../../ftr_provider_context';
import { asyncForEach } from '../../helpers';
@ -17,25 +18,98 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const find = getService('find');
const { alertControls } = getPageObjects(['alertControls']);
const PageObjects = getPageObjects(['home', 'common']);
const supertest = getService('supertest');
const browser = getService('browser');
const customThresholdRule = {
tags: [],
params: {
criteria: [
{
comparator: '>',
metrics: [
{
name: 'A',
aggType: 'count',
},
],
threshold: [1],
timeSize: 15,
timeUnit: 'm',
},
],
alertOnNoData: false,
alertOnGroupDisappear: false,
searchConfiguration: {
query: {
query: '',
language: 'kuery',
},
index: '90943e30-9a47-11e8-b64d-95841ca0b247',
},
},
schedule: {
interval: '1m',
},
consumer: 'logs',
name: 'Custom threshold rule',
rule_type_id: 'observability.rules.custom_threshold',
actions: [
{
group: 'custom_threshold.fired',
id: 'my-server-log',
params: {
message:
'{{context.reason}}\n\n{{rule.name}} is active.\n\n[View alert details]({{context.alertDetailsUrl}})\n',
level: 'info',
},
frequency: {
summary: false,
notify_when: RuleNotifyWhen.THROTTLE,
throttle: '1m',
},
},
],
alert_delay: {
active: 1,
},
};
describe('Observability alerts >', function () {
this.tags('includeFirefox');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const observability = getService('observability');
let customThresholdRuleId: string;
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
const setup = async () => {
await observability.alerts.common.setKibanaTimeZoneToUTC();
await observability.alerts.common.navigateToTimeWithData();
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await PageObjects.home.addSampleDataSet('logs');
const { body: createdRule } = await supertest
.post('/api/alerting/rule')
.set('kbn-xsrf', 'foo')
.send(customThresholdRule)
.expect(200);
customThresholdRuleId = createdRule.id;
};
await setup();
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await supertest.delete(`/api/alerting/rule/${customThresholdRuleId}`).set('kbn-xsrf', 'foo');
await PageObjects.home.removeSampleDataSet('logs');
});
describe('Alerts table', () => {
@ -83,6 +157,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
});
it('Autocompletion works', async () => {
await browser.refresh();
await observability.alerts.common.typeInQueryBar('kibana.alert.s');
await observability.alerts.common.clickOnQueryBar();
await testSubjects.existOrFail('autocompleteSuggestion-field-kibana.alert.start-');

View file

@ -1,89 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { obsOnlySpacesAll } from '../../../common/lib/authentication/users';
import type { User } from '../../../common/lib/authentication/types';
import type { FtrProviderContext } from '../../../common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../common/lib/authentication/spaces';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const retry = getService('retry');
const SPACE1 = 'space1';
const TEST_URL = '/internal/rac/alerts/aad_fields';
const getAADFieldsByRuleType = async (
user: User,
ruleTypeId: string,
expectedStatusCode: number = 200
) => {
const resp = await supertestWithoutAuth
.get(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`)
.query({ ruleTypeId })
.auth(user.username, user.password)
.set('kbn-xsrf', 'true')
.expect(expectedStatusCode);
return resp.body;
};
describe('Alert - Get AAD fields by ruleType', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
describe('Users:', () => {
it(`${obsOnlySpacesAll.username} should be able to get browser fields for o11y rule type ids`, async () => {
await retry.try(async () => {
const aadFields = await getAADFieldsByRuleType(
obsOnlySpacesAll,
'metrics.alert.threshold'
);
expect(aadFields.slice(0, 2)).to.eql(expectedResult);
expect(aadFields.length > 2).to.be(true);
for (const field of aadFields) {
expectToBeFieldDescriptor(field);
}
});
});
});
});
};
const expectedResult = [
{
name: '_id',
type: 'string',
searchable: false,
aggregatable: false,
readFromDocValues: false,
metadata_field: true,
},
{
name: '_index',
type: 'string',
searchable: false,
aggregatable: false,
readFromDocValues: false,
metadata_field: true,
},
];
const expectToBeFieldDescriptor = (field: Record<string, unknown>) => {
expect('name' in field).to.be(true);
expect('type' in field).to.be(true);
expect('searchable' in field).to.be(true);
expect('aggregatable' in field).to.be(true);
expect('readFromDocValues' in field).to.be(true);
expect('metadata_field' in field).to.be(true);
};

View file

@ -15,10 +15,42 @@ import { getSpaceUrlPrefix } from '../../../common/lib/authentication/spaces';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const retry = getService('retry');
const SPACE1 = 'space1';
const TEST_URL = '/internal/rac/alerts/browser_fields';
const esQueryRule = {
tags: [],
params: {
searchType: 'esQuery',
timeWindowSize: 5,
timeWindowUnit: 'm',
threshold: [1],
thresholdComparator: '>',
size: 100,
esQuery: '{\n "query":{\n "match_all" : {}\n }\n }',
aggType: 'count',
groupBy: 'all',
termSize: 5,
excludeHitsFromPreviousRun: false,
sourceFields: [],
index: ['.kibana_alerting_cases'],
timeField: 'updated_at',
},
schedule: {
interval: '1m',
},
consumer: 'stackAlerts',
name: 'Elasticsearch query rule',
rule_type_id: '.es-query',
actions: [],
alert_delay: {
active: 1,
},
};
const getBrowserFieldsByFeatureId = async (
user: User,
ruleTypeIds: string[],
@ -40,56 +72,31 @@ export default ({ getService }: FtrProviderContext) => {
'.es-query',
'xpack.ml.anomaly_detection_alert',
];
let esQueryRuleId: string;
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
const { body: createdESRule } = await supertest
.post('/api/alerting/rule')
.set('kbn-xsrf', 'foo')
.send(esQueryRule)
.expect(200);
esQueryRuleId = createdESRule.id;
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
await supertest.delete(`/api/alerting/rule/${esQueryRuleId}`).set('kbn-xsrf', 'foo');
});
describe('Users:', () => {
it(`${obsOnlySpacesAll.username} should be able to get browser fields for o11y ruleTypeIds that has access to`, async () => {
const resp = await getBrowserFieldsByFeatureId(obsOnlySpacesAll, ruleTypeIds);
it(`${obsOnlySpacesAll.username} should be able to get non empty browser fields for all o11y ruleTypeIds`, async () => {
await retry.try(async () => {
const resp = await getBrowserFieldsByFeatureId(superUser, ruleTypeIds);
expect(Object.keys(resp.browserFields)).toEqual([
'base',
'agent',
'cloud',
'container',
'error',
'host',
'kibana',
'observer',
'orchestrator',
'service',
'tls',
'url',
]);
});
it(`${superUser.username} should be able to get browser fields for all o11y ruleTypeIds`, async () => {
const resp = await getBrowserFieldsByFeatureId(superUser, ruleTypeIds);
expect(Object.keys(resp.browserFields)).toEqual([
'base',
'agent',
'anomaly',
'cloud',
'container',
'error',
'host',
'kibana',
'location',
'monitor',
'observer',
'orchestrator',
'service',
'slo',
'tls',
'url',
]);
expect(Object.keys(resp.browserFields)).toEqual(['base', 'event', 'kibana']);
});
});
it(`${superUser.username} should NOT be able to get browser fields for siem rule types`, async () => {

View file

@ -30,6 +30,5 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
loadTestFile(require.resolve('./search_strategy'));
loadTestFile(require.resolve('./get_browser_fields_by_rule_type_ids'));
loadTestFile(require.resolve('./get_alert_summary'));
loadTestFile(require.resolve('./get_aad_fields_by_rule_type'));
});
};