From 02e744f54dc15d4f4896273215e4dc7a90ad9e5b Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 31 Oct 2023 17:27:53 -0400 Subject: [PATCH] [RAM] add observability feature for server less (#168636) ## Summary FIX => https://github.com/elastic/kibana/issues/168034 ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: mgiota Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.oblt.yml | 5 + .../src/rule_types/o11y_rules.ts | 7 + x-pack/plugins/alerting/server/config.ts | 7 + .../create_get_alert_indices_alias.test.ts | 2 + x-pack/plugins/alerting/server/plugin.ts | 1 + .../server/rule_type_registry.test.ts | 59 ++++++ .../alerting/server/rule_type_registry.ts | 11 +- .../saved_objects/is_rule_exportable.test.ts | 2 + .../apm/common/rules/apm_rule_types.ts | 8 +- .../common/rules/get_all_groupby_fields.ts | 2 +- x-pack/plugins/apm/common/rules/schema.ts | 3 +- .../rule_types/register_apm_rule_types.ts | 3 +- .../ui_components/alerting_flyout/index.tsx | 7 +- .../utils/get_initial_alert_values.test.ts | 6 +- .../utils/get_initial_alert_values.ts | 6 +- .../components/app/alerts_overview/index.tsx | 2 +- .../alerting_popover_flyout.tsx | 2 +- x-pack/plugins/apm/server/feature.ts | 6 +- .../anomaly/register_anomaly_rule_type.ts | 5 +- .../get_error_count_chart_preview.ts | 2 +- .../register_error_count_rule_type.ts | 2 +- .../get_transaction_duration_chart_preview.ts | 6 +- ...register_transaction_duration_rule_type.ts | 4 +- ...et_transaction_error_rate_chart_preview.ts | 2 +- ...gister_transaction_error_rate_rule_type.ts | 4 +- .../server/routes/alerts/test_utils/index.ts | 4 +- .../plugins/observability/common/constants.ts | 1 + .../components/alert_flyout.tsx | 1 + .../hooks/slo/use_fetch_active_alerts.ts | 3 +- .../slo_details/components/header_control.tsx | 1 + .../components/slo_detail_alerts.tsx | 2 +- .../slo_edit/components/slo_edit_form.tsx | 1 + .../pages/slos/components/slo_list_item.tsx | 1 + x-pack/plugins/observability/server/index.ts | 1 + x-pack/plugins/observability/server/plugin.ts | 64 ++++++ x-pack/plugins/stack_alerts/server/plugin.ts | 1 - .../sections/rule_form/rule_form.tsx | 29 ++- .../rule_form_consumer_selection.test.tsx | 13 ++ .../rule_form_consumer_selection.tsx | 10 +- .../triggers_actions_ui/public/types.ts | 1 + .../tests/alerts/anomaly_alert.spec.ts | 2 +- .../alerts/error_count_threshold.spec.ts | 2 +- .../alerts/helpers/alerting_api_helper.ts | 2 +- .../tests/alerts/transaction_duration.spec.ts | 3 +- .../alerts/transaction_error_rate.spec.ts | 2 +- .../service_group_count.spec.ts | 3 +- .../tests/services/service_alerts.spec.ts | 3 +- .../transactions_groups_alerts.spec.ts | 3 +- .../api_integration/services/alerting_api.ts | 15 +- .../api_integration/services/index.ts | 2 + .../api_integration/services/slo_api.ts | 89 ++++++++ .../common/alerting/alert_documents.ts | 2 +- .../alerting/helpers/alerting_api_helper.ts | 17 ++ .../test_suites/common/alerting/rules.ts | 2 +- .../common/alerting/summary_actions.ts | 6 +- .../burn_rate_rule/burn_rate_rule.ts | 194 ++++++++++++++++++ .../custom_threshold_rule/avg_pct_fired.ts | 10 +- .../custom_threshold_rule/avg_pct_no_data.ts | 10 +- .../custom_eq_avg_bytes_fired.ts | 10 +- .../documents_count_fired.ts | 10 +- .../custom_threshold_rule/group_by_fired.ts | 10 +- .../es_query_rule/es_query_rule.ts | 113 ++++++++++ .../test_suites/observability/index.ts | 2 + .../observability/rules/rules_list.ts | 63 +++++- 64 files changed, 787 insertions(+), 85 deletions(-) create mode 100644 x-pack/test_serverless/api_integration/services/slo_api.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/burn_rate_rule/burn_rate_rule.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index d38e510edbea8..a2d58e930f4cf 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -25,6 +25,11 @@ xpack.serverless.plugin.developer.projectSwitcher.currentType: 'observability' ## Disable adding the component template `.fleet_agent_id_verification-1` to every index template for each datastream for each integration xpack.fleet.agentIdVerificationEnabled: false +## Enable the capability for the observability feature ID in the serverless environment to take ownership of the rules. +## The value need to be a featureId observability Or stackAlerts Or siem +xpack.alerting.rules.overwriteProducer: 'observability' +xpack.observability.createO11yGenericFeatureId: true + ## APM Serverless Onboarding flow xpack.apm.serverlessOnboarding: true diff --git a/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts index 86c5ef3f2e646..b88e0f708a980 100644 --- a/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts +++ b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts @@ -7,3 +7,10 @@ */ export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; + +export enum ApmRuleType { + ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat. + TransactionErrorRate = 'apm.transaction_error_rate', + TransactionDuration = 'apm.transaction_duration', + Anomaly = 'apm.anomaly', +} diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index 3ec1edb2f3c1c..a49c393da1e95 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -39,6 +39,13 @@ const rulesSchema = schema.object({ enforce: schema.boolean({ defaultValue: false }), // if enforce is false, only warnings will be shown }), maxScheduledPerMinute: schema.number({ defaultValue: 10000, max: 10000, min: 0 }), + overwriteProducer: schema.maybe( + schema.oneOf([ + schema.literal('observability'), + schema.literal('siem'), + schema.literal('stackAlerts'), + ]) + ), run: schema.object({ timeout: schema.maybe(schema.string({ validate: validateDurationSchema })), actions: schema.object({ diff --git a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts index 52b9a12cc35c0..9a8109977962c 100644 --- a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts @@ -15,6 +15,7 @@ import { ILicenseState } from './license_state'; import { licenseStateMock } from './license_state.mock'; import { schema } from '@kbn/config-schema'; import { createGetAlertIndicesAliasFn } from './create_get_alert_indices_alias'; +import { AlertingConfig } from '../config'; describe('createGetAlertIndicesAliasFn', () => { const logger = loggingSystemMock.create().get(); @@ -23,6 +24,7 @@ describe('createGetAlertIndicesAliasFn', () => { const inMemoryMetrics = inMemoryMetricsMock.create(); const ruleTypeRegistryParams: ConstructorOptions = { + config: {} as AlertingConfig, logger, taskManager, taskRunnerFactory: new TaskRunnerFactory(), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 80b71a4dd8a7c..21a66a7c2b0c4 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -296,6 +296,7 @@ export class AlertingPlugin { } const ruleTypeRegistry = new RuleTypeRegistry({ + config: this.config, logger: this.logger, taskManager: plugins.taskManager, taskRunnerFactory: this.taskRunnerFactory, diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index 1de3f7c0be400..8e01e7d05cbd1 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -17,6 +17,7 @@ import { inMemoryMetricsMock } from './monitoring/in_memory_metrics.mock'; import { alertsServiceMock } from './alerts_service/alerts_service.mock'; import { schema } from '@kbn/config-schema'; import { RecoveredActionGroupId } from '../common'; +import { AlertingConfig } from './config'; const logger = loggingSystemMock.create().get(); let mockedLicenseState: jest.Mocked; @@ -30,6 +31,7 @@ beforeEach(() => { jest.clearAllMocks(); mockedLicenseState = licenseStateMock.create(); ruleTypeRegistryParams = { + config: {} as AlertingConfig, logger, taskManager, taskRunnerFactory: new TaskRunnerFactory(), @@ -582,6 +584,63 @@ describe('Create Lifecycle', () => { expect(alertsService.register).not.toHaveBeenCalled(); }); + + test('registers rule with no overwrite on producer', () => { + const ruleType: RuleType = { + id: 'test', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + category: 'test', + producer: 'alerts', + ruleTaskTimeout: '20m', + validate: { + params: { validate: (params) => params }, + }, + }; + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + registry.register(ruleType); + expect(registry.get('test').producer).toEqual('alerts'); + }); + }); + + describe('register() with overwriteProducer', () => { + test('registers rule and overwrite producer', () => { + const ruleType: RuleType = { + id: 'test', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + category: 'test', + producer: 'alerts', + ruleTaskTimeout: '20m', + validate: { + params: { validate: (params) => params }, + }, + }; + const registry = new RuleTypeRegistry({ + ...ruleTypeRegistryParams, + config: { rules: { overwriteProducer: 'observability' } } as unknown as AlertingConfig, + }); + registry.register(ruleType); + expect(registry.get('test').producer).toEqual('observability'); + }); }); describe('get()', () => { diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index 1d147c2cb3d6a..d73d28950970e 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -39,8 +39,10 @@ import { InMemoryMetrics } from './monitoring'; import { AlertingRulesConfig } from '.'; import { AlertsService } from './alerts_service/alerts_service'; import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers'; +import { AlertingConfig } from './config'; export interface ConstructorOptions { + config: AlertingConfig; logger: Logger; taskManager: TaskManagerSetupContract; taskRunnerFactory: TaskRunnerFactory; @@ -148,6 +150,7 @@ export type UntypedNormalizedRuleType = NormalizedRuleType< >; export class RuleTypeRegistry { + private readonly config: AlertingConfig; private readonly logger: Logger; private readonly taskManager: TaskManagerSetupContract; private readonly ruleTypes: Map = new Map(); @@ -159,6 +162,7 @@ export class RuleTypeRegistry { private readonly alertsService: AlertsService | null; constructor({ + config, logger, taskManager, taskRunnerFactory, @@ -168,6 +172,7 @@ export class RuleTypeRegistry { inMemoryMetrics, alertsService, }: ConstructorOptions) { + this.config = config; this.logger = logger; this.taskManager = taskManager; this.taskRunnerFactory = taskRunnerFactory; @@ -277,7 +282,7 @@ export class RuleTypeRegistry { ActionGroupIds, RecoveryActionGroupId, AlertData - >(ruleType); + >(ruleType, this.config); this.ruleTypes.set( ruleTypeIdSchema.validate(ruleType.id), @@ -457,7 +462,8 @@ function augmentActionGroupsWithReserved< ActionGroupIds, RecoveryActionGroupId, AlertData - > + >, + config: AlertingConfig ): NormalizedRuleType< Params, ExtractedParams, @@ -505,6 +511,7 @@ function augmentActionGroupsWithReserved< return { ...ruleType, + ...(config?.rules?.overwriteProducer ? { producer: config.rules.overwriteProducer } : {}), actionGroups: [...actionGroups, ...reservedActionGroups], recoveryActionGroup: recoveryActionGroup ?? RecoveredActionGroup, validLegacyConsumers: getRuleTypeIdValidLegacyConsumers(id), diff --git a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts index 6a799370b546f..442dcf0a9469d 100644 --- a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts @@ -15,6 +15,7 @@ import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import { isRuleExportable } from './is_rule_exportable'; import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { AlertingConfig } from '../config'; let ruleTypeRegistryParams: ConstructorOptions; let logger: MockedLogger; @@ -27,6 +28,7 @@ beforeEach(() => { mockedLicenseState = licenseStateMock.create(); logger = loggerMock.create(); ruleTypeRegistryParams = { + config: {} as AlertingConfig, logger: loggingSystemMock.create().get(), taskManager, alertsService: null, diff --git a/x-pack/plugins/apm/common/rules/apm_rule_types.ts b/x-pack/plugins/apm/common/rules/apm_rule_types.ts index b18ea1f0c49ce..39659f146db8b 100644 --- a/x-pack/plugins/apm/common/rules/apm_rule_types.ts +++ b/x-pack/plugins/apm/common/rules/apm_rule_types.ts @@ -16,6 +16,7 @@ import type { ActionGroup } from '@kbn/alerting-plugin/common'; import { formatDurationFromTimeUnitChar } from '@kbn/observability-plugin/common'; import { ML_ANOMALY_SEVERITY } from '@kbn/ml-anomaly-utils/anomaly_severity'; import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { ERROR_GROUP_ID, ERROR_GROUP_NAME, @@ -28,13 +29,6 @@ import { getEnvironmentLabel } from '../environment_filter_values'; export const APM_SERVER_FEATURE_ID = 'apm'; -export enum ApmRuleType { - ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat. - TransactionErrorRate = 'apm.transaction_error_rate', - TransactionDuration = 'apm.transaction_duration', - Anomaly = 'apm.anomaly', -} - export enum AggregationType { Avg = 'avg', P95 = '95th', diff --git a/x-pack/plugins/apm/common/rules/get_all_groupby_fields.ts b/x-pack/plugins/apm/common/rules/get_all_groupby_fields.ts index 696a30df3a78c..055fb1984defe 100644 --- a/x-pack/plugins/apm/common/rules/get_all_groupby_fields.ts +++ b/x-pack/plugins/apm/common/rules/get_all_groupby_fields.ts @@ -6,7 +6,7 @@ */ import { union } from 'lodash'; -import { ApmRuleType } from './apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts index 9041e4c0c8e5a..c38fe85561696 100644 --- a/x-pack/plugins/apm/common/rules/schema.ts +++ b/x-pack/plugins/apm/common/rules/schema.ts @@ -7,7 +7,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { ML_ANOMALY_SEVERITY } from '@kbn/ml-anomaly-utils/anomaly_severity'; -import { AggregationType, ApmRuleType } from './apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; +import { AggregationType } from './apm_rule_types'; export const searchConfigurationSchema = schema.object({ query: schema.object({ diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/register_apm_rule_types.ts b/x-pack/plugins/apm/public/components/alerting/rule_types/register_apm_rule_types.ts index e8f2c6185ebd6..d3ddffc131e14 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/register_apm_rule_types.ts +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/register_apm_rule_types.ts @@ -7,13 +7,12 @@ import { i18n } from '@kbn/i18n'; import { lazy } from 'react'; -import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ApmRuleType } from '@kbn/rule-data-utils'; import type { ObservabilityRuleTypeRegistry } from '@kbn/observability-plugin/public'; import { getAlertUrlErrorCount, getAlertUrlTransaction, } from '../../../../common/utils/formatters'; -import { ApmRuleType } from '../../../../common/rules/apm_rule_types'; import { anomalyMessage, errorCountMessage, diff --git a/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx index 0180b2f940a58..95820bf8f84d4 100644 --- a/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx @@ -7,10 +7,8 @@ import React, { useCallback, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - ApmRuleType, - APM_SERVER_FEATURE_ID, -} from '../../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; +import { APM_SERVER_FEATURE_ID } from '../../../../../common/rules/apm_rule_types'; import { getInitialAlertValues } from '../../utils/get_initial_alert_values'; import { ApmPluginStartDeps } from '../../../../plugin'; import { useServiceName } from '../../../../hooks/use_service_name'; @@ -70,6 +68,7 @@ export function AlertingFlyout(props: Props) { start, end, } as AlertMetadata, + useRuleProducer: true, }), /* eslint-disable-next-line react-hooks/exhaustive-deps */ [ diff --git a/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.test.ts b/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.test.ts index e590e66c2c7ee..c82e36f0cd935 100644 --- a/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.test.ts +++ b/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.test.ts @@ -6,10 +6,8 @@ */ import { getInitialAlertValues } from './get_initial_alert_values'; -import { - ApmRuleType, - RULE_TYPES_CONFIG, -} from '../../../../common/rules/apm_rule_types'; +import { RULE_TYPES_CONFIG } from '../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; test('handles null rule type and undefined service name', () => { expect(getInitialAlertValues(null, undefined)).toEqual({ tags: ['apm'] }); diff --git a/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.ts b/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.ts index 452a4561f34e1..4a5e53f436e1a 100644 --- a/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.ts +++ b/x-pack/plugins/apm/public/components/alerting/utils/get_initial_alert_values.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { - ApmRuleType, - RULE_TYPES_CONFIG, -} from '../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; +import { RULE_TYPES_CONFIG } from '../../../../common/rules/apm_rule_types'; export function getInitialAlertValues( ruleType: ApmRuleType | null, diff --git a/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx b/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx index 759e878ad4cab..dbf2d94b20699 100644 --- a/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/alerts_overview/index.tsx @@ -110,7 +110,7 @@ export function AlertsOverview() { } id={'service-overview-alerts'} configurationId={AlertConsumers.OBSERVABILITY} - featureIds={[AlertConsumers.APM]} + featureIds={[AlertConsumers.APM, AlertConsumers.OBSERVABILITY]} query={esQuery} showAlertStatusWithFlapping /> diff --git a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/alerting_popover_flyout.tsx index 2a6514a9da7c9..5b8fc78c91eba 100644 --- a/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/alerting_popover_flyout.tsx +++ b/x-pack/plugins/apm/public/components/routing/app_root/apm_header_action_menu/alerting_popover_flyout.tsx @@ -13,7 +13,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { ApmRuleType } from '../../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { AlertingFlyout } from '../../../alerting/ui_components/alerting_flyout'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 79136af2f8856..5a0ba552a0c18 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -14,10 +14,8 @@ import { } from '@kbn/licensing-plugin/server'; import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices'; -import { - ApmRuleType, - APM_SERVER_FEATURE_ID, -} from '../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; +import { APM_SERVER_FEATURE_ID } from '../common/rules/apm_rule_types'; const ruleTypes = Object.values(ApmRuleType); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts index 5e761229be8f6..407b128c03240 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts @@ -21,6 +21,7 @@ import { ALERT_EVALUATION_VALUE, ALERT_REASON, ALERT_SEVERITY, + ApmRuleType, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; @@ -43,7 +44,7 @@ import { } from '../../../../../common/environment_filter_values'; import { ANOMALY_ALERT_SEVERITY_TYPES, - ApmRuleType, + APM_SERVER_FEATURE_ID, formatAnomalyReason, RULE_TYPES_CONFIG, } from '../../../../../common/rules/apm_rule_types'; @@ -94,7 +95,7 @@ export function registerAnomalyRuleType({ ], }, category: DEFAULT_APP_CATEGORIES.observability.id, - producer: 'apm', + producer: APM_SERVER_FEATURE_ID, minimumLicenseRequired: 'basic', isExportable: true, executor: async ({ diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts index a1fcb98d7a323..653c5562804c0 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts @@ -11,6 +11,7 @@ import { termQuery, } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, @@ -21,7 +22,6 @@ import { environmentQuery } from '../../../../../common/utils/environment_query' import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; import { getGroupByTerms } from '../utils/get_groupby_terms'; import { getAllGroupByFields } from '../../../../../common/rules/get_all_groupby_fields'; -import { ApmRuleType } from '../../../../../common/rules/apm_rule_types'; import { BarSeriesDataMap, getFilteredBarSeries, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index 6a0a5b8fdbbe6..ba053dbcb8577 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -18,6 +18,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, + ApmRuleType, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; import { @@ -34,7 +35,6 @@ import { SERVICE_NAME, } from '../../../../../common/es_fields/apm'; import { - ApmRuleType, APM_SERVER_FEATURE_ID, formatErrorCountReason, RULE_TYPES_CONFIG, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts index db968bdfcc2ce..0384ad24839f2 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts @@ -11,10 +11,8 @@ import { rangeQuery, termQuery, } from '@kbn/observability-plugin/server'; -import { - AggregationType, - ApmRuleType, -} from '../../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; +import { AggregationType } from '../../../../../common/rules/apm_rule_types'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 3789a55e6e4e0..ef0cb2beb21c9 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -24,6 +24,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, + ApmRuleType, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; @@ -38,7 +39,6 @@ import { TRANSACTION_TYPE, } from '../../../../../common/es_fields/apm'; import { - ApmRuleType, APM_SERVER_FEATURE_ID, formatTransactionDurationReason, RULE_TYPES_CONFIG, @@ -87,9 +87,9 @@ export const transactionDurationActionVariables = [ export function registerTransactionDurationRuleType({ alerting, + apmConfig, ruleDataClient, getApmIndices, - apmConfig, logger, basePath, }: RegisterRuleDependencies) { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts index 853252e6ef453..10f5457380719 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts @@ -10,7 +10,7 @@ import { rangeQuery, termQuery, } from '@kbn/observability-plugin/server'; -import { ApmRuleType } from '../../../../../common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index 7e3c7bce8baf3..30c8c6fb96b96 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -23,6 +23,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, + ApmRuleType, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; @@ -39,7 +40,6 @@ import { } from '../../../../../common/es_fields/apm'; import { EventOutcome } from '../../../../../common/event_outcome'; import { - ApmRuleType, APM_SERVER_FEATURE_ID, formatTransactionErrorRateReason, RULE_TYPES_CONFIG, @@ -83,9 +83,9 @@ export const transactionErrorRateActionVariables = [ export function registerTransactionErrorRateRuleType({ alerting, alertsLocator, + apmConfig, basePath, getApmIndices, - apmConfig, logger, ruleDataClient, }: RegisterRuleDependencies) { diff --git a/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts b/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts index b4b5692708456..a447ab2a75d4b 100644 --- a/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts +++ b/x-pack/plugins/apm/server/routes/alerts/test_utils/index.ts @@ -56,7 +56,9 @@ export const createRuleTypeMocks = () => { publicBaseUrl: 'http://localhost:5601/eyr', serverBasePath: '/eyr', } as IBasePath, - apmConfig: { searchAggregatedTransactions: true } as any as APMConfig, + apmConfig: { + searchAggregatedTransactions: true, + } as any as APMConfig, getApmIndices: async () => ({ error: 'apm-*', transaction: 'apm-*', diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index 11f23808d8fa3..b10eafd3e608f 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -60,4 +60,5 @@ export const observabilityAlertFeatureIds: ValidFeatureId[] = [ export const observabilityRuleCreationValidConsumers: RuleCreationValidConsumer[] = [ AlertConsumers.INFRASTRUCTURE, AlertConsumers.LOGS, + AlertConsumers.OBSERVABILITY, ]; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx index ba39bcd7c4861..dc4ef4407c82c 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx @@ -39,6 +39,7 @@ export function AlertFlyout(props: Props) { series: props.series, }, validConsumers: observabilityRuleCreationValidConsumers, + useRuleProducer: true, }), // eslint-disable-next-line react-hooks/exhaustive-deps [triggersActionsUI, onCloseFlyout] diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts index 7f4e6fdce5b2b..31cf30ca03a1d 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts @@ -9,6 +9,7 @@ import { useQuery } from '@tanstack/react-query'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; import { ALL_VALUE, SLOResponse } from '@kbn/slo-schema'; +import { AlertConsumers } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; import { useKibana } from '../../utils/kibana_react'; import { sloKeys } from './query_key_factory'; @@ -80,7 +81,7 @@ export function useFetchActiveAlerts({ sloIdsAndInstanceIds = [] }: Params): Use try { const response = await http.post(`${BASE_RAC_ALERTS_API_PATH}/find`, { body: JSON.stringify({ - feature_ids: ['slo'], + feature_ids: [AlertConsumers.SLO, AlertConsumers.OBSERVABILITY], size: 0, query: { bool: { diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index 02556efaf396a..b8c18aa457b48 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -250,6 +250,7 @@ export function HeaderControl({ isLoading, slo }: Props) { canChangeTrigger={false} onClose={onCloseRuleFlyout} initialValues={{ name: `${slo.name} burn rate`, params: { sloId: slo.id } }} + useRuleProducer /> ) : null} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_detail_alerts.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_detail_alerts.tsx index 74d59bf74081e..f5d84d19b5c4e 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_detail_alerts.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_detail_alerts.tsx @@ -32,7 +32,7 @@ export function SloDetailsAlerts({ slo }: Props) { configurationId={AlertConsumers.OBSERVABILITY} id={ALERTS_TABLE_ID} data-test-subj="alertTable" - featureIds={[AlertConsumers.SLO]} + featureIds={[AlertConsumers.SLO, AlertConsumers.OBSERVABILITY]} query={{ bool: { filter: [ diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index b8fc9bdaa0a02..6627f910a7c27 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -264,6 +264,7 @@ export function SloEditForm({ slo }: Props) { ruleTypeId={SLO_BURN_RATE_RULE_TYPE_ID} onClose={handleCloseRuleFlyout} onSave={handleCloseRuleFlyout} + useRuleProducer /> ) : null} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index cd4b6e760ea09..ae843977a0ee7 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -276,6 +276,7 @@ export function SloListItem({ onClose={() => { setIsAddRuleFlyoutOpen(false); }} + useRuleProducer /> ) : null} diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index e05740ea9785c..96231115e3fa2 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -57,6 +57,7 @@ const configSchema = schema.object({ groupByPageSize: schema.number({ defaultValue: 10_000 }), }), enabled: schema.boolean({ defaultValue: true }), + createO11yGenericFeatureId: schema.boolean({ defaultValue: false }), }); export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index b4a81fb11e850..c034a41fbf2d5 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -26,6 +26,11 @@ import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/serve import { SharePluginSetup } from '@kbn/share-plugin/server'; import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { + ApmRuleType, + ES_QUERY_ID, + OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, +} from '@kbn/rule-data-utils'; import { ObservabilityConfig } from '.'; import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants'; @@ -72,6 +77,13 @@ interface PluginStart { const sloRuleTypes = [SLO_BURN_RATE_RULE_TYPE_ID]; +const o11yRuleTypes = [ + SLO_BURN_RATE_RULE_TYPE_ID, + OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + ES_QUERY_ID, + ...Object.values(ApmRuleType), +]; + export class ObservabilityPlugin implements Plugin { private logger: Logger; @@ -180,6 +192,58 @@ export class ObservabilityPlugin implements Plugin { }); } + if (config.createO11yGenericFeatureId) { + plugins.features.registerKibanaFeature({ + id: observabilityFeatureId, + name: i18n.translate('xpack.observability.nameFeatureTitle', { + defaultMessage: 'Observability', + }), + order: 1000, + category: DEFAULT_APP_CATEGORIES.observability, + app: [observabilityFeatureId], + catalogue: [observabilityFeatureId], + alerting: o11yRuleTypes, + privileges: { + all: { + app: [observabilityFeatureId], + catalogue: [observabilityFeatureId], + api: ['rac'], + savedObject: { + all: [], + read: [], + }, + alerting: { + rule: { + all: o11yRuleTypes, + }, + alert: { + all: o11yRuleTypes, + }, + }, + ui: ['read', 'write'], + }, + read: { + app: [observabilityFeatureId], + catalogue: [observabilityFeatureId], + api: ['rac'], + savedObject: { + all: [], + read: [], + }, + alerting: { + rule: { + read: o11yRuleTypes, + }, + alert: { + read: o11yRuleTypes, + }, + }, + ui: ['read'], + }, + }, + }); + } + const { ruleDataService } = plugins.ruleRegistry; const savedObjectTypes = [SO_SLO_TYPE]; diff --git a/x-pack/plugins/stack_alerts/server/plugin.ts b/x-pack/plugins/stack_alerts/server/plugin.ts index b4b25bb983195..f8ea52d1dcf47 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.ts @@ -22,7 +22,6 @@ export class AlertingBuiltinsPlugin public setup(core: CoreSetup, { alerting, features }: StackAlertsDeps) { features.registerKibanaFeature(BUILT_IN_ALERTS_FEATURE); - registerBuiltInRuleTypes({ logger: this.logger, data: core diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 7988dcefc1bef..c88e3beba0cad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -59,6 +59,7 @@ import { RuleActionAlertsFilterProperty, } from '@kbn/alerting-plugin/common'; import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common'; +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RuleReducerAction, InitialRule } from './rule_reducer'; import { RuleTypeModel, @@ -250,11 +251,11 @@ export const RuleForm = ({ validConsumers, }) ) - .filter((item) => - rule.consumer === ALERTS_FEATURE_ID + .filter((item) => { + return rule.consumer === ALERTS_FEATURE_ID ? !item.ruleTypeModel.requiresAppContext - : item.ruleType!.producer === rule.consumer - ); + : item.ruleType!.producer === rule.consumer; + }); const availableRuleTypesResult = getAvailableRuleTypes(ruleTypes); setAvailableRuleTypes(availableRuleTypesResult); @@ -274,9 +275,13 @@ export const RuleForm = ({ }, new Map() ); - setSolutions( - new Map([...solutionsResult.entries()].sort(([, a], [, b]) => a.localeCompare(b))) - ); + const solutionsEntries = [...solutionsResult.entries()]; + const isOnlyO11y = + availableRuleTypesResult.length === 1 && + availableRuleTypesResult.every((rt) => rt.ruleType.producer === AlertConsumers.OBSERVABILITY); + if (!isOnlyO11y) { + setSolutions(new Map(solutionsEntries.sort(([, a], [, b]) => a.localeCompare(b)))); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ ruleTypes, @@ -400,6 +405,16 @@ export const RuleForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [ruleTypeRegistry, availableRuleTypes, searchText, JSON.stringify(solutionsFilter)]); + useEffect(() => { + if (ruleTypeModel) { + const ruleType = ruleTypes.find((rt) => rt.id === ruleTypeModel.id); + if (ruleType && useRuleProducer && !MULTI_CONSUMER_RULE_TYPE_IDS.includes(ruleType.id)) { + setConsumer(ruleType.producer as RuleCreationValidConsumer); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ruleTypeModel, ruleTypes]); + const authorizedConsumers = useMemo(() => { // If the app context provides a consumer, we assume that consumer is // is what we set for all rules that is created in that context diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx index ee7af0446c9ec..34724e8d745bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx @@ -80,4 +80,17 @@ describe('RuleFormConsumerSelectionModal', () => { expect(mockOnChange).toHaveBeenLastCalledWith('stackAlerts'); expect(screen.queryByTestId('ruleFormConsumerSelect')).not.toBeInTheDocument(); }); + + it('should display nothing if observability is one of the consumer', () => { + render( + + ); + + expect(mockOnChange).toHaveBeenLastCalledWith('observability'); + expect(screen.queryByTestId('ruleFormConsumerSelect')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx index f5f92c72fa521..ac2c61bedd78b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx @@ -121,13 +121,15 @@ export const RuleFormConsumerSelection = (props: RuleFormConsumerSelectionProps) }, []); useEffect(() => { - if (formattedSelectOptions.length === 1) { - onChange(formattedSelectOptions[0].value as RuleCreationValidConsumer); + if (consumers.length === 1) { + onChange(consumers[0] as RuleCreationValidConsumer); + } else if (consumers.includes(AlertConsumers.OBSERVABILITY)) { + onChange(AlertConsumers.OBSERVABILITY as RuleCreationValidConsumer); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [formattedSelectOptions]); + }, [consumers]); - if (formattedSelectOptions.length <= 1) { + if (consumers.length <= 1 || consumers.includes(AlertConsumers.OBSERVABILITY)) { return null; } return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index dd65104407247..9c9f08bc77b98 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -838,4 +838,5 @@ export interface NotifyWhenSelectOptions { export type RuleCreationValidConsumer = | typeof AlertConsumers.LOGS | typeof AlertConsumers.INFRASTRUCTURE + | typeof AlertConsumers.OBSERVABILITY | typeof STACK_ALERTS_FEATURE_ID; diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts index cae769750b4cc..e7e29c34c6634 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { range } from 'lodash'; diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts index 1cfcbc165b17f..239cff2e98af5 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { errorCountActionVariables } from '@kbn/apm-plugin/server/routes/alerts/rule_types/error_count/register_error_count_rule_type'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; diff --git a/x-pack/test/apm_api_integration/tests/alerts/helpers/alerting_api_helper.ts b/x-pack/test/apm_api_integration/tests/alerts/helpers/alerting_api_helper.ts index 53d8819ef1745..f7eb20824b24c 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/helpers/alerting_api_helper.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/helpers/alerting_api_helper.ts @@ -9,7 +9,7 @@ import { Client, errors } from '@elastic/elasticsearch'; import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import pRetry from 'p-retry'; import type { SuperTest, Test } from 'supertest'; -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { ApmRuleParamsType } from '@kbn/apm-plugin/common/rules/schema'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts index bb8c9833bbc6d..c4b5110e42fb4 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { transactionDurationActionVariables } from '@kbn/apm-plugin/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts index 056ee4e272d61..a3f69827cf7a8 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { transactionErrorRateActionVariables } from '@kbn/apm-plugin/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; diff --git a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts index 14a5af9478b0b..dc909ffebd024 100644 --- a/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_groups/service_group_count/service_group_count.spec.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { diff --git a/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts index 0b611042d5acc..4ba8ae48bd00d 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_alerts.spec.ts @@ -5,7 +5,8 @@ * 2.0. */ import expect from '@kbn/expect'; -import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts index 23c680fd81ae7..752ed822a8c99 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts @@ -11,7 +11,8 @@ import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregati import { ApmDocumentType, ApmTransactionDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { ApmRuleType } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createApmRule, diff --git a/x-pack/test_serverless/api_integration/services/alerting_api.ts b/x-pack/test_serverless/api_integration/services/alerting_api.ts index 668dcdab82aa0..6f47757a6e30b 100644 --- a/x-pack/test_serverless/api_integration/services/alerting_api.ts +++ b/x-pack/test_serverless/api_integration/services/alerting_api.ts @@ -12,7 +12,7 @@ import type { import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; - +import { SloBurnRateRuleParams } from './slo_api'; import { FtrProviderContext } from '../ftr_provider_context'; export function AlertingApiProvider({ getService }: FtrProviderContext) { @@ -117,7 +117,7 @@ export function AlertingApiProvider({ getService }: FtrProviderContext) { }: { ruleTypeId: string; name: string; - params: MetricThresholdParams | ThresholdParams; + params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams; actions?: any[]; tags?: any[]; schedule?: { interval: string }; @@ -140,5 +140,16 @@ export function AlertingApiProvider({ getService }: FtrProviderContext) { }); return body; }, + + async findRule(ruleId: string) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + const response = await supertest + .get('/api/alerting/rules/_find') + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + return response.body.data.find((obj: any) => obj.id === ruleId); + }, }; } diff --git a/x-pack/test_serverless/api_integration/services/index.ts b/x-pack/test_serverless/api_integration/services/index.ts index dd59c42545c03..00be2fa67fbd7 100644 --- a/x-pack/test_serverless/api_integration/services/index.ts +++ b/x-pack/test_serverless/api_integration/services/index.ts @@ -13,6 +13,7 @@ import { AlertingApiProvider } from './alerting_api'; import { SamlToolsProvider } from './saml_tools'; import { DataViewApiProvider } from './data_view_api'; import { SvlCasesServiceProvider } from './svl_cases'; +import { SloApiProvider } from './slo_api'; export const services = { // deployment agnostic FTR services @@ -24,6 +25,7 @@ export const services = { samlTools: SamlToolsProvider, dataViewApi: DataViewApiProvider, svlCases: SvlCasesServiceProvider, + sloApi: SloApiProvider, }; export type InheritedFtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test_serverless/api_integration/services/slo_api.ts b/x-pack/test_serverless/api_integration/services/slo_api.ts new file mode 100644 index 0000000000000..da3ec1710a56b --- /dev/null +++ b/x-pack/test_serverless/api_integration/services/slo_api.ts @@ -0,0 +1,89 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M'; + +interface Duration { + value: number; + unit: DurationUnit; +} + +interface WindowSchema { + id: string; + burnRateThreshold: number; + maxBurnRateThreshold: number; + longWindow: Duration; + shortWindow: Duration; + actionGroup: string; +} + +export interface SloBurnRateRuleParams { + sloId: string; + windows: WindowSchema[]; +} + +interface SloParams { + id: string; + name: string; + description: string; + indicator: { + type: 'sli.kql.custom'; + params: { + index: string; + good: string; + total: string; + timestampField: string; + }; + }; + timeWindow: { + duration: string; + type: string; + }; + budgetingMethod: string; + objective: { + target: number; + }; + groupBy: string; +} + +export function SloApiProvider({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const retry = getService('retry'); + const requestTimeout = 30 * 1000; + const retryTimeout = 120 * 1000; + + return { + async create(slo: SloParams) { + const { body } = await supertest + .post(`/api/observability/slos`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .send(slo); + + return body; + }, + + async delete() {}, + + async waitForSloCreated({ sloId }: { sloId: string }) { + if (!sloId) { + throw new Error(`'sloId is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertest + .get(`/api/observability/slos/${sloId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .timeout(requestTimeout); + + return response.body; + }); + }, + }; +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts index 4069f4d63d9eb..feddf08dc39b1 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts @@ -164,7 +164,7 @@ export default function ({ getService }: FtrProviderContext) { groupBy: 'all', searchType: 'esQuery', }, - [ALERT_RULE_PRODUCER]: 'stackAlerts', + [ALERT_RULE_PRODUCER]: hits1[ALERT_RULE_PRODUCER], [ALERT_RULE_REVISION]: 0, [ALERT_RULE_TYPE_ID]: '.es-query', [ALERT_RULE_TAGS]: [], diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts index d20eda10fbf68..171ac839da4bc 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts @@ -437,3 +437,20 @@ export async function snoozeRule({ .expect(204); return body; } + +export async function findRule({ + supertest, + ruleId, +}: { + supertest: SuperTest; + ruleId: string; +}) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + const response = await supertest + .get(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + return response.body || {}; +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts index 9efc2025210c5..3ef5ff6a4785f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts @@ -962,7 +962,7 @@ function validateEventLog(event: any, params: ValidateEventLogParams) { id: params.ruleId, license: 'basic', category: params.ruleTypeId, - ruleset: 'stackAlerts', + ruleset: event?.rule.ruleset, name: params.name, }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts index 1885c951b81f1..107203a61b958 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts @@ -190,7 +190,7 @@ export default function ({ getService }: FtrProviderContext) { groupBy: 'all', searchType: 'esQuery', }, - [ALERT_RULE_PRODUCER]: 'stackAlerts', + [ALERT_RULE_PRODUCER]: alertDocument[ALERT_RULE_PRODUCER], [ALERT_RULE_REVISION]: 0, [ALERT_RULE_TYPE_ID]: '.es-query', [ALERT_RULE_TAGS]: [], @@ -311,7 +311,7 @@ export default function ({ getService }: FtrProviderContext) { groupBy: 'all', searchType: 'esQuery', }, - [ALERT_RULE_PRODUCER]: 'stackAlerts', + [ALERT_RULE_PRODUCER]: alertDocument[ALERT_RULE_PRODUCER], [ALERT_RULE_REVISION]: 0, [ALERT_RULE_TYPE_ID]: '.es-query', [ALERT_RULE_TAGS]: [], @@ -520,7 +520,7 @@ export default function ({ getService }: FtrProviderContext) { groupBy: 'all', searchType: 'esQuery', }, - [ALERT_RULE_PRODUCER]: 'stackAlerts', + [ALERT_RULE_PRODUCER]: alertDocument[ALERT_RULE_PRODUCER], [ALERT_RULE_REVISION]: 0, [ALERT_RULE_TYPE_ID]: '.es-query', [ALERT_RULE_TAGS]: [], diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/burn_rate_rule/burn_rate_rule.ts b/x-pack/test_serverless/api_integration/test_suites/observability/burn_rate_rule/burn_rate_rule.ts new file mode 100644 index 0000000000000..7d863d6cdbc66 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/burn_rate_rule/burn_rate_rule.ts @@ -0,0 +1,194 @@ +/* + * 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. + */ +/* + * 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 { cleanup, generate } from '@kbn/infra-forge'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + const sloApi = getService('sloApi'); + + describe('Burn rate rule', () => { + const RULE_TYPE_ID = 'slo.rules.burnRate'; + // DATE_VIEW should match the index template: + // x-pack/packages/kbn-infra-forge/src/data_sources/composable/template.json + const DATE_VIEW = 'kbn-data-forge-fake_hosts'; + const ALERT_ACTION_INDEX = 'alert-action-slo'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger }); + await dataViewApi.create({ + name: DATE_VIEW, + id: DATA_VIEW_ID, + title: DATE_VIEW, + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'rule.id': ruleId } }, + conflicts: 'proceed', + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await supertest + .delete('/api/observability/slos/my-custom-id') + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Slo Burn rate API test', + indexName: ALERT_ACTION_INDEX, + }); + + await sloApi.create({ + id: 'my-custom-id', + name: 'my custom name', + description: 'my custom description', + indicator: { + type: 'sli.kql.custom', + params: { + index: infraDataIndex, + good: 'system.cpu.total.norm.pct > 1', + total: 'system.cpu.total.norm.pct: *', + timestampField: '@timestamp', + }, + }, + timeWindow: { + duration: '7d', + type: 'rolling', + }, + budgetingMethod: 'occurrences', + objective: { + target: 0.999, + }, + groupBy: '*', + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'observability', + name: 'SLO Burn Rate rule', + ruleTypeId: RULE_TYPE_ID, + schedule: { + interval: '1m', + }, + params: { + sloId: 'my-custom-id', + windows: [ + { + id: '1', + actionGroup: 'slo.burnRate.alert', + burnRateThreshold: 14.4, + maxBurnRateThreshold: 720, + longWindow: { + value: 1, + unit: 'h', + }, + shortWindow: { + value: 5, + unit: 'm', + }, + }, + { + id: '2', + actionGroup: 'slo.burnRate.high', + burnRateThreshold: 6, + maxBurnRateThreshold: 120, + longWindow: { + value: 6, + unit: 'h', + }, + shortWindow: { + value: 30, + unit: 'm', + }, + }, + { + id: '3', + actionGroup: 'slo.burnRate.medium', + burnRateThreshold: 3, + maxBurnRateThreshold: 30, + longWindow: { + value: 24, + unit: 'h', + }, + shortWindow: { + value: 120, + unit: 'm', + }, + }, + { + id: '4', + actionGroup: 'slo.burnRate.low', + burnRateThreshold: 1, + maxBurnRateThreshold: 10, + longWindow: { + value: 72, + unit: 'h', + }, + shortWindow: { + value: 360, + unit: 'm', + }, + }, + ], + }, + actions: [], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts index 90b602900341b..4aea7f5d670db 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'apm', + consumer: 'observability', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -139,6 +139,12 @@ export default function ({ getService }: FtrProviderContext) { expect(executionStatus).to.be('active'); }); + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -149,7 +155,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (Technical Preview)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'apm'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts index f41aa7c9d3172..2d6e8d63afbf2 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -68,7 +68,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'apm', + consumer: 'observability', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -125,6 +125,12 @@ export default function ({ getService }: FtrProviderContext) { expect(executionStatus).to.be('active'); }); + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -135,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (Technical Preview)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'apm'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 5aaa276afc25b..1f02377919a8d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -84,7 +84,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'apm', + consumer: 'observability', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -143,6 +143,12 @@ export default function ({ getService }: FtrProviderContext) { expect(executionStatus).to.be('active'); }); + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -153,7 +159,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (Technical Preview)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'apm'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts index ee8c266287e20..5c34258de8c15 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -78,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'apm', + consumer: 'observability', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -133,6 +133,12 @@ export default function ({ getService }: FtrProviderContext) { expect(executionStatus).to.be('active'); }); + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -143,7 +149,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (Technical Preview)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'apm'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index 3139b40587ddf..864b35de6f3d4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -88,7 +88,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'apm', + consumer: 'observability', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -150,6 +150,12 @@ export default function ({ getService }: FtrProviderContext) { expect(executionStatus).to.be('active'); }); + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -162,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (Technical Preview)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'apm'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts new file mode 100644 index 0000000000000..9f7c72889623b --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts @@ -0,0 +1,113 @@ +/* + * 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. + */ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { createEsQueryRule } from '../../common/alerting/helpers/alerting_api_helper'; +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const alertingApi = getService('alertingApi'); + + describe('ElasticSearch query rule', () => { + const RULE_TYPE_ID = '.es-query'; + const ALERT_ACTION_INDEX = 'alert-action-es-query'; + let actionId: string; + let ruleId: string; + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'rule.id': ruleId } }, + conflicts: 'proceed', + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX]); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Alerting API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createEsQueryRule({ + supertest, + consumer: 'observability', + name: 'always fire', + ruleTypeId: RULE_TYPE_ID, + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + actions: [ + { + group: 'query matched', + id: actionId, + params: { + documents: [ + { + ruleId: '{{rule.id}}', + ruleName: '{{rule.name}}', + ruleParams: '{{rule.params}}', + spaceId: '{{rule.spaceId}}', + tags: '{{rule.tags}}', + alertId: '{{alert.id}}', + alertActionGroup: '{{alert.actionGroup}}', + instanceContextValue: '{{context.instanceContextValue}}', + instanceStateValue: '{{state.instanceStateValue}}', + }, + ], + }, + frequency: { + notify_when: 'onActiveAlert', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.ts index 72cb01c03fd2f..54d4e41c1f892 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.ts @@ -16,5 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./telemetry/telemetry_config')); loadTestFile(require.resolve('./apm_api_integration/feature_flags.ts')); loadTestFile(require.resolve('./cases')); + loadTestFile(require.resolve('./burn_rate_rule/burn_rate_rule')); + loadTestFile(require.resolve('./es_query_rule/es_query_rule')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts index 4b78147ef4941..917e953cb2aec 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts @@ -15,6 +15,7 @@ import { createIndexConnector, snoozeRule, createLatencyThresholdRule, + createEsQueryRule, } from '../../../../api_integration/test_suites/common/alerting/helpers/alerting_api_helper'; export default ({ getPageObject, getService }: FtrProviderContext) => { @@ -90,6 +91,64 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await svlCommonPage.forceLogout(); }); + it('should create an ES Query Rule and display it when consumer is observability', async () => { + const esQuery = await createEsQueryRule({ + supertest, + name: 'ES Query', + consumer: 'observability', + ruleTypeId: '.es-query', + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + }); + ruleIdList = [esQuery.id]; + + await refreshRulesList(); + const searchResults = await svlTriggersActionsUI.getRulesList(); + expect(searchResults.length).toEqual(1); + expect(searchResults[0].name).toEqual('ES QueryElasticsearch query'); + }); + + it('should create an ES Query rule but not display it when consumer is stackAlerts', async () => { + const esQuery = await createEsQueryRule({ + supertest, + name: 'ES Query', + consumer: 'stackAlerts', + ruleTypeId: '.es-query', + params: { + size: 100, + thresholdComparator: '>', + threshold: [-1], + index: ['alert-test-data'], + timeField: 'date', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + timeWindowSize: 20, + timeWindowUnit: 's', + }, + }); + ruleIdList = [esQuery.id]; + + await refreshRulesList(); + await testSubjects.missingOrFail('rule-row'); + }); + + it('should create and display an APM latency rule', async () => { + const apmLatency = await createLatencyThresholdRule({ supertest, name: 'Apm latency' }); + ruleIdList = [apmLatency.id]; + + await refreshRulesList(); + const searchResults = await svlTriggersActionsUI.getRulesList(); + expect(searchResults.length).toEqual(1); + expect(searchResults[0].name).toEqual('Apm latencyLatency threshold'); + }); + it('should display rules in alphabetical order', async () => { const rule1 = await createRule({ supertest, @@ -600,7 +659,9 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await testSubjects.click('ruleTypeFilterButton'); } - expect(await (await testSubjects.find('ruleType0Group')).getVisibleText()).toEqual('Apm'); + expect(await (await testSubjects.find('ruleType0Group')).getVisibleText()).toEqual( + 'Observability' + ); }); await testSubjects.click('ruleTypeapm.anomalyFilterOption');