From eb4b92d876376d280c41dc352915e5e6e975cc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:30:42 -0500 Subject: [PATCH 1/8] [APM] adding API test for alert error count threshold (#150764) Add new api test for error count threshold `tests/alerts/error_count_threshold.spec.ts` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/lib/timerange.ts | 18 +- .../kbn-apm-synthtrace-client/tsconfig.json | 3 + .../common/rules/default_action_message.ts | 60 +++++++ x-pack/plugins/apm/common/rules/schema.ts | 71 ++++++++ .../rule_types/register_apm_rule_types.ts | 58 ++---- .../anomaly/register_anomaly_rule_type.ts | 21 +-- .../register_error_count_rule_type.test.ts | 6 +- .../register_error_count_rule_type.ts | 48 +++-- ...ter_transaction_duration_rule_type.test.ts | 2 +- ...register_transaction_duration_rule_type.ts | 74 ++++---- ...r_transaction_error_rate_rule_type.test.ts | 2 +- ...gister_transaction_error_rate_rule_type.ts | 56 +++--- .../tests/alerts/alerting_api_helper.ts | 117 +++++++++++++ .../tests/alerts/anomaly_alert.spec.ts | 52 +++--- .../alerts/error_count_threshold.spec.ts | 165 ++++++++++++++++++ .../tests/alerts/wait_for_rule_status.ts | 73 ++++---- .../service_group_count.spec.ts | 42 ++--- .../tests/services/service_alerts.spec.ts | 42 ++--- 18 files changed, 624 insertions(+), 286 deletions(-) create mode 100644 x-pack/plugins/apm/common/rules/default_action_message.ts create mode 100644 x-pack/plugins/apm/common/rules/schema.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/alerting_api_helper.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts diff --git a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts index 2dd0659f9cc19..0817ea3d0e34f 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import datemath from '@kbn/datemath'; import type { Moment } from 'moment'; import { Interval } from './interval'; @@ -23,12 +23,22 @@ export class Timerange { type DateLike = Date | number | Moment | string; -function getDateFrom(date: DateLike): Date { +function getDateFrom(date: DateLike, now: Date): Date { if (date instanceof Date) return date; + + if (typeof date === 'string') { + const parsed = datemath.parse(date, { forceNow: now }); + if (parsed && parsed.isValid()) { + return parsed.toDate(); + } + } + if (typeof date === 'number' || typeof date === 'string') return new Date(date); + return date.toDate(); } -export function timerange(from: Date | number | Moment, to: Date | number | Moment) { - return new Timerange(getDateFrom(from), getDateFrom(to)); +export function timerange(from: DateLike, to: DateLike) { + const now = new Date(); + return new Timerange(getDateFrom(from, now), getDateFrom(to, now)); } diff --git a/packages/kbn-apm-synthtrace-client/tsconfig.json b/packages/kbn-apm-synthtrace-client/tsconfig.json index 8d1c9cae899a3..8286fda7455b0 100644 --- a/packages/kbn-apm-synthtrace-client/tsconfig.json +++ b/packages/kbn-apm-synthtrace-client/tsconfig.json @@ -11,5 +11,8 @@ "include": ["**/*.ts"], "exclude": [ "target/**/*", + ], + "kbn_references": [ + "@kbn/datemath", ] } diff --git a/x-pack/plugins/apm/common/rules/default_action_message.ts b/x-pack/plugins/apm/common/rules/default_action_message.ts new file mode 100644 index 0000000000000..503bc1ca3cd26 --- /dev/null +++ b/x-pack/plugins/apm/common/rules/default_action_message.ts @@ -0,0 +1,60 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const errorCountMessage = i18n.translate( + 'xpack.apm.alertTypes.errorCount.defaultActionMessage', + { + defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: + +- Service name: \\{\\{context.serviceName\\}\\} +- Environment: \\{\\{context.environment\\}\\} +- Threshold: \\{\\{context.threshold\\}\\} +- Triggered value: \\{\\{context.triggerValue\\}\\} errors over the last \\{\\{context.interval\\}\\}`, + } +); + +export const transactionDurationMessage = i18n.translate( + 'xpack.apm.alertTypes.transactionDuration.defaultActionMessage', + { + defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: + +- Service name: \\{\\{context.serviceName\\}\\} +- Type: \\{\\{context.transactionType\\}\\} +- Environment: \\{\\{context.environment\\}\\} +- Latency threshold: \\{\\{context.threshold\\}\\}ms +- Latency observed: \\{\\{context.triggerValue\\}\\} over the last \\{\\{context.interval\\}\\}`, + } +); + +export const transactionErrorRateMessage = i18n.translate( + 'xpack.apm.alertTypes.transactionErrorRate.defaultActionMessage', + { + defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: + +- Service name: \\{\\{context.serviceName\\}\\} +- Type: \\{\\{context.transactionType\\}\\} +- Environment: \\{\\{context.environment\\}\\} +- Threshold: \\{\\{context.threshold\\}\\}% +- Triggered value: \\{\\{context.triggerValue\\}\\}% of errors over the last \\{\\{context.interval\\}\\}`, + } +); + +export const anomalyMessage = i18n.translate( + 'xpack.apm.alertTypes.transactionDurationAnomaly.defaultActionMessage', + { + defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: + +- Service name: \\{\\{context.serviceName\\}\\} +- Type: \\{\\{context.transactionType\\}\\} +- Environment: \\{\\{context.environment\\}\\} +- Severity threshold: \\{\\{context.threshold\\}\\} +- Severity value: \\{\\{context.triggerValue\\}\\} +`, + } +); diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts new file mode 100644 index 0000000000000..58a5b40da41f2 --- /dev/null +++ b/x-pack/plugins/apm/common/rules/schema.ts @@ -0,0 +1,71 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { ANOMALY_SEVERITY } from '../ml_constants'; +import { AggregationType, ApmRuleType } from './apm_rule_types'; + +export const errorCountParamsSchema = schema.object({ + windowSize: schema.number(), + windowUnit: schema.string(), + threshold: schema.number(), + serviceName: schema.maybe(schema.string()), + environment: schema.string(), +}); + +export const transactionDurationParamsSchema = schema.object({ + serviceName: schema.maybe(schema.string()), + transactionType: schema.maybe(schema.string()), + windowSize: schema.number(), + windowUnit: schema.string(), + threshold: schema.number(), + aggregationType: schema.oneOf([ + schema.literal(AggregationType.Avg), + schema.literal(AggregationType.P95), + schema.literal(AggregationType.P99), + ]), + environment: schema.string(), +}); + +export const anomalyParamsSchema = schema.object({ + serviceName: schema.maybe(schema.string()), + transactionType: schema.maybe(schema.string()), + windowSize: schema.number(), + windowUnit: schema.string(), + environment: schema.string(), + anomalySeverityType: schema.oneOf([ + schema.literal(ANOMALY_SEVERITY.CRITICAL), + schema.literal(ANOMALY_SEVERITY.MAJOR), + schema.literal(ANOMALY_SEVERITY.MINOR), + schema.literal(ANOMALY_SEVERITY.WARNING), + ]), +}); + +export const transactionErrorRateParamsSchema = schema.object({ + windowSize: schema.number(), + windowUnit: schema.string(), + threshold: schema.number(), + transactionType: schema.maybe(schema.string()), + serviceName: schema.maybe(schema.string()), + environment: schema.string(), +}); + +type ErrorCountParamsType = TypeOf; +type TransactionDurationParamsType = TypeOf< + typeof transactionDurationParamsSchema +>; +type AnomalyParamsType = TypeOf; +type TransactionErrorRateParamsType = TypeOf< + typeof transactionErrorRateParamsSchema +>; + +export interface ApmRuleParamsType { + [ApmRuleType.TransactionDuration]: TransactionDurationParamsType; + [ApmRuleType.ErrorCount]: ErrorCountParamsType; + [ApmRuleType.Anomaly]: AnomalyParamsType; + [ApmRuleType.TransactionErrorRate]: TransactionErrorRateParamsType; +} 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 ed9971307bf64..f355ea4c2f6eb 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 @@ -14,6 +14,12 @@ import { getAlertUrlTransaction, } from '../../../../common/utils/formatters'; import { ApmRuleType } from '../../../../common/rules/apm_rule_types'; +import { + anomalyMessage, + errorCountMessage, + transactionDurationMessage, + transactionErrorRateMessage, +} from '../../../../common/rules/default_action_message'; // copied from elasticsearch_fieldnames.ts to limit page load bundle size const SERVICE_ENVIRONMENT = 'service.environment'; @@ -54,17 +60,7 @@ export function registerApmRuleTypes( ) ), requiresAppContext: false, - defaultActionMessage: i18n.translate( - 'xpack.apm.alertTypes.errorCount.defaultActionMessage', - { - defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: - -- Service name: \\{\\{context.serviceName\\}\\} -- Environment: \\{\\{context.environment\\}\\} -- Threshold: \\{\\{context.threshold\\}\\} errors -- Triggered value: \\{\\{context.triggerValue\\}\\} errors over the last \\{\\{context.interval\\}\\}`, - } - ), + defaultActionMessage: errorCountMessage, }); observabilityRuleTypeRegistry.register({ @@ -104,18 +100,7 @@ export function registerApmRuleTypes( ) ), requiresAppContext: false, - defaultActionMessage: i18n.translate( - 'xpack.apm.alertTypes.transactionDuration.defaultActionMessage', - { - defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: - -- Service name: \\{\\{context.serviceName\\}\\} -- Type: \\{\\{context.transactionType\\}\\} -- Environment: \\{\\{context.environment\\}\\} -- Latency threshold: \\{\\{context.threshold\\}\\}ms -- Latency observed: \\{\\{context.triggerValue\\}\\} over the last \\{\\{context.interval\\}\\}`, - } - ), + defaultActionMessage: transactionDurationMessage, }); observabilityRuleTypeRegistry.register({ @@ -153,18 +138,7 @@ export function registerApmRuleTypes( ) ), requiresAppContext: false, - defaultActionMessage: i18n.translate( - 'xpack.apm.alertTypes.transactionErrorRate.defaultActionMessage', - { - defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: - -- Service name: \\{\\{context.serviceName\\}\\} -- Type: \\{\\{context.transactionType\\}\\} -- Environment: \\{\\{context.environment\\}\\} -- Threshold: \\{\\{context.threshold\\}\\}% -- Triggered value: \\{\\{context.triggerValue\\}\\}% of errors over the last \\{\\{context.interval\\}\\}`, - } - ), + defaultActionMessage: transactionErrorRateMessage, }); observabilityRuleTypeRegistry.register({ @@ -199,18 +173,6 @@ export function registerApmRuleTypes( ) ), requiresAppContext: false, - defaultActionMessage: i18n.translate( - 'xpack.apm.alertTypes.transactionDurationAnomaly.defaultActionMessage', - { - defaultMessage: `\\{\\{alertName\\}\\} alert is firing because of the following conditions: - -- Service name: \\{\\{context.serviceName\\}\\} -- Type: \\{\\{context.transactionType\\}\\} -- Environment: \\{\\{context.environment\\}\\} -- Severity threshold: \\{\\{context.threshold\\}\\} -- Severity value: \\{\\{context.triggerValue\\}\\} -`, - } - ), + defaultActionMessage: anomalyMessage, }); } 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 529efb583b7dd..a10d803ef6d86 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 @@ -5,7 +5,6 @@ * 2.0. */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { schema } from '@kbn/config-schema'; import { KibanaRequest } from '@kbn/core/server'; import datemath from '@kbn/datemath'; import type { ESSearchResponse } from '@kbn/es-types'; @@ -36,7 +35,6 @@ import { getEnvironmentEsField, getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; -import { ANOMALY_SEVERITY } from '../../../../../common/ml_constants'; import { ANOMALY_ALERT_SEVERITY_TYPES, ApmRuleType, @@ -49,20 +47,7 @@ import { getMLJobs } from '../../../service_map/get_service_anomalies'; import { apmActionVariables } from '../../action_variables'; import { RegisterRuleDependencies } from '../../register_apm_rule_types'; import { getServiceGroupFieldsForAnomaly } from './get_service_group_fields_for_anomaly'; - -const paramsSchema = schema.object({ - serviceName: schema.maybe(schema.string()), - transactionType: schema.maybe(schema.string()), - windowSize: schema.number(), - windowUnit: schema.string(), - environment: schema.string(), - anomalySeverityType: schema.oneOf([ - schema.literal(ANOMALY_SEVERITY.CRITICAL), - schema.literal(ANOMALY_SEVERITY.MAJOR), - schema.literal(ANOMALY_SEVERITY.MINOR), - schema.literal(ANOMALY_SEVERITY.WARNING), - ]), -}); +import { anomalyParamsSchema } from '../../../../../common/rules/schema'; const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.Anomaly]; @@ -86,9 +71,7 @@ export function registerAnomalyRuleType({ name: ruleTypeConfig.name, actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, - validate: { - params: paramsSchema, - }, + validate: { params: anomalyParamsSchema }, actionVariables: { context: [ ...(observability.getAlertDetailsConfig()?.apm.enabled diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts index 705804cfa74f4..6d6752eba0ce5 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts @@ -146,7 +146,7 @@ describe('Error count alert', () => { threshold: 2, triggerValue: 5, reason: 'Error count is 5 in the last 5 mins for foo. Alert when > 2.', - interval: '5m', + interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo', }); @@ -159,7 +159,7 @@ describe('Error count alert', () => { threshold: 2, triggerValue: 4, reason: 'Error count is 4 in the last 5 mins for foo. Alert when > 2.', - interval: '5m', + interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2', }); @@ -172,7 +172,7 @@ describe('Error count alert', () => { reason: 'Error count is 3 in the last 5 mins for bar. Alert when > 2.', threshold: 2, triggerValue: 3, - interval: '5m', + interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar', }); 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 54bfb00d468b0..c811e71fe1f17 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 @@ -5,54 +5,49 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; -import { firstValueFrom } from 'rxjs'; +import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; +import { + formatDurationFromTimeUnitChar, + ProcessorEvent, + TimeUnitChar, +} from '@kbn/observability-plugin/common'; +import { termQuery } from '@kbn/observability-plugin/server'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; -import { termQuery } from '@kbn/observability-plugin/server'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; - +import { firstValueFrom } from 'rxjs'; import { ENVIRONMENT_NOT_DEFINED, getEnvironmentEsField, getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; -import { getAlertUrlErrorCount } from '../../../../../common/utils/formatters'; -import { - ApmRuleType, - APM_SERVER_FEATURE_ID, - RULE_TYPES_CONFIG, - formatErrorCountReason, -} from '../../../../../common/rules/apm_rule_types'; import { PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../../../common/es_fields/apm'; +import { + ApmRuleType, + APM_SERVER_FEATURE_ID, + formatErrorCountReason, + RULE_TYPES_CONFIG, +} from '../../../../../common/rules/apm_rule_types'; +import { errorCountParamsSchema } from '../../../../../common/rules/schema'; import { environmentQuery } from '../../../../../common/utils/environment_query'; +import { getAlertUrlErrorCount } from '../../../../../common/utils/formatters'; import { getApmIndices } from '../../../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from '../../action_variables'; import { alertingEsClient } from '../../alerting_es_client'; import { RegisterRuleDependencies } from '../../register_apm_rule_types'; import { - getServiceGroupFieldsAgg, getServiceGroupFields, + getServiceGroupFieldsAgg, } from '../get_service_group_fields'; -const paramsSchema = schema.object({ - windowSize: schema.number(), - windowUnit: schema.string(), - threshold: schema.number(), - serviceName: schema.maybe(schema.string()), - environment: schema.string(), -}); - const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.ErrorCount]; export function registerErrorCountRuleType({ @@ -74,9 +69,7 @@ export function registerErrorCountRuleType({ name: ruleTypeConfig.name, actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, - validate: { - params: paramsSchema, - }, + validate: { params: errorCountParamsSchema }, actionVariables: { context: [ ...(observability.getAlertDetailsConfig()?.apm.enabled @@ -214,7 +207,10 @@ export function registerErrorCountRuleType({ .scheduleActions(ruleTypeConfig.defaultActionGroupId, { alertDetailsUrl, environment: getEnvironmentLabel(environment), - interval: `${ruleParams.windowSize}${ruleParams.windowUnit}`, + interval: formatDurationFromTimeUnitChar( + ruleParams.windowSize, + ruleParams.windowUnit as TimeUnitChar + ), reason: alertReason, serviceName, threshold: ruleParams.threshold, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts index cebf71f1bad97..dcd8994860ae7 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts @@ -60,7 +60,7 @@ describe('registerTransactionDurationRuleType', () => { 'http://localhost:5601/eyr/app/observability/alerts/' ), environment: 'Not defined', - interval: `5m`, + interval: `5 mins`, reason: 'Avg. latency is 5,500 ms in the last 5 mins for opbeans-java. Alert when > 3,000 ms.', transactionType: 'request', 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 803945568494b..bc11cee03a506 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 @@ -6,41 +6,46 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { schema } from '@kbn/config-schema'; +import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; +import { + formatDurationFromTimeUnitChar, + ProcessorEvent, + TimeUnitChar, +} from '@kbn/observability-plugin/common'; +import { asDuration } from '@kbn/observability-plugin/common/utils/formatters'; +import { termQuery } from '@kbn/observability-plugin/server'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, } from '@kbn/rule-data-utils'; -import { firstValueFrom } from 'rxjs'; -import { asDuration } from '@kbn/observability-plugin/common/utils/formatters'; -import { termQuery } from '@kbn/observability-plugin/server'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; -import { getAlertUrlTransaction } from '../../../../../common/utils/formatters'; +import { firstValueFrom } from 'rxjs'; import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions'; import { - ApmRuleType, - AggregationType, - RULE_TYPES_CONFIG, - APM_SERVER_FEATURE_ID, - formatTransactionDurationReason, -} from '../../../../../common/rules/apm_rule_types'; + ENVIRONMENT_NOT_DEFINED, + getEnvironmentEsField, + getEnvironmentLabel, +} from '../../../../../common/environment_filter_values'; import { PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, SERVICE_NAME, TRANSACTION_TYPE, - SERVICE_ENVIRONMENT, } from '../../../../../common/es_fields/apm'; import { - ENVIRONMENT_NOT_DEFINED, - getEnvironmentEsField, - getEnvironmentLabel, -} from '../../../../../common/environment_filter_values'; + ApmRuleType, + APM_SERVER_FEATURE_ID, + formatTransactionDurationReason, + RULE_TYPES_CONFIG, +} from '../../../../../common/rules/apm_rule_types'; +import { transactionDurationParamsSchema } from '../../../../../common/rules/schema'; import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { getDurationFormatter } from '../../../../../common/utils/formatters'; +import { + getAlertUrlTransaction, + getDurationFormatter, +} from '../../../../../common/utils/formatters'; import { getDocumentTypeFilterForTransactions, getDurationFieldForTransactions, @@ -49,28 +54,14 @@ import { getApmIndices } from '../../../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from '../../action_variables'; import { alertingEsClient } from '../../alerting_es_client'; import { RegisterRuleDependencies } from '../../register_apm_rule_types'; -import { - averageOrPercentileAgg, - getMultiTermsSortOrder, -} from './average_or_percentile_agg'; import { getServiceGroupFields, getServiceGroupFieldsAgg, } from '../get_service_group_fields'; - -const paramsSchema = schema.object({ - serviceName: schema.maybe(schema.string()), - transactionType: schema.maybe(schema.string()), - windowSize: schema.number(), - windowUnit: schema.string(), - threshold: schema.number(), - aggregationType: schema.oneOf([ - schema.literal(AggregationType.Avg), - schema.literal(AggregationType.P95), - schema.literal(AggregationType.P99), - ]), - environment: schema.string(), -}); +import { + averageOrPercentileAgg, + getMultiTermsSortOrder, +} from './average_or_percentile_agg'; const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.TransactionDuration]; @@ -92,9 +83,7 @@ export function registerTransactionDurationRuleType({ name: ruleTypeConfig.name, actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, - validate: { - params: paramsSchema, - }, + validate: { params: transactionDurationParamsSchema }, actionVariables: { context: [ ...(observability.getAlertDetailsConfig()?.apm.enabled @@ -289,7 +278,10 @@ export function registerTransactionDurationRuleType({ .scheduleActions(ruleTypeConfig.defaultActionGroupId, { alertDetailsUrl, environment: environmentLabel, - interval: `${ruleParams.windowSize}${ruleParams.windowUnit}`, + interval: formatDurationFromTimeUnitChar( + ruleParams.windowSize, + ruleParams.windowUnit as TimeUnitChar + ), reason, serviceName, threshold: ruleParams.threshold, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts index 38de7d48cce4c..02eb14e782df3 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts @@ -131,7 +131,7 @@ describe('Transaction error rate alert', () => { 'Failed transactions is 10% in the last 5 mins for foo. Alert when > 10%.', threshold: 10, triggerValue: '10', - interval: '5m', + interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo', }); 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 308ed32e3b5a1..df94a3e8a3c75 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 @@ -5,31 +5,28 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; -import { firstValueFrom } from 'rxjs'; +import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; +import { + formatDurationFromTimeUnitChar, + ProcessorEvent, + TimeUnitChar, +} from '@kbn/observability-plugin/common'; +import { asPercent } from '@kbn/observability-plugin/common/utils/formatters'; +import { termQuery } from '@kbn/observability-plugin/server'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_REASON, } from '@kbn/rule-data-utils'; import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; -import { asPercent } from '@kbn/observability-plugin/common/utils/formatters'; -import { termQuery } from '@kbn/observability-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils'; +import { firstValueFrom } from 'rxjs'; +import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions'; import { ENVIRONMENT_NOT_DEFINED, getEnvironmentEsField, getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; -import { getAlertUrlTransaction } from '../../../../../common/utils/formatters'; -import { - ApmRuleType, - RULE_TYPES_CONFIG, - APM_SERVER_FEATURE_ID, - formatTransactionErrorRateReason, -} from '../../../../../common/rules/apm_rule_types'; import { EVENT_OUTCOME, PROCESSOR_EVENT, @@ -38,28 +35,28 @@ import { TRANSACTION_TYPE, } from '../../../../../common/es_fields/apm'; import { EventOutcome } from '../../../../../common/event_outcome'; -import { asDecimalOrInteger } from '../../../../../common/utils/formatters'; +import { + ApmRuleType, + APM_SERVER_FEATURE_ID, + formatTransactionErrorRateReason, + RULE_TYPES_CONFIG, +} from '../../../../../common/rules/apm_rule_types'; +import { transactionErrorRateParamsSchema } from '../../../../../common/rules/schema'; import { environmentQuery } from '../../../../../common/utils/environment_query'; +import { + asDecimalOrInteger, + getAlertUrlTransaction, +} from '../../../../../common/utils/formatters'; +import { getDocumentTypeFilterForTransactions } from '../../../../lib/helpers/transactions'; import { getApmIndices } from '../../../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from '../../action_variables'; import { alertingEsClient } from '../../alerting_es_client'; import { RegisterRuleDependencies } from '../../register_apm_rule_types'; -import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions'; -import { getDocumentTypeFilterForTransactions } from '../../../../lib/helpers/transactions'; import { getServiceGroupFields, getServiceGroupFieldsAgg, } from '../get_service_group_fields'; -const paramsSchema = schema.object({ - windowSize: schema.number(), - windowUnit: schema.string(), - threshold: schema.number(), - transactionType: schema.maybe(schema.string()), - serviceName: schema.maybe(schema.string()), - environment: schema.string(), -}); - const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.TransactionErrorRate]; export function registerTransactionErrorRateRuleType({ @@ -81,9 +78,7 @@ export function registerTransactionErrorRateRuleType({ name: ruleTypeConfig.name, actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, - validate: { - params: paramsSchema, - }, + validate: { params: transactionErrorRateParamsSchema }, actionVariables: { context: [ ...(observability.getAlertDetailsConfig()?.apm.enabled @@ -285,7 +280,10 @@ export function registerTransactionErrorRateRuleType({ .scheduleActions(ruleTypeConfig.defaultActionGroupId, { alertDetailsUrl, environment: getEnvironmentLabel(environment), - interval: `${ruleParams.windowSize}${ruleParams.windowUnit}`, + interval: formatDurationFromTimeUnitChar( + ruleParams.windowSize, + ruleParams.windowUnit as TimeUnitChar + ), reason: reasonMessage, serviceName, threshold: ruleParams.threshold, diff --git a/x-pack/test/apm_api_integration/tests/alerts/alerting_api_helper.ts b/x-pack/test/apm_api_integration/tests/alerts/alerting_api_helper.ts new file mode 100644 index 0000000000000..b6c5b891b8353 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/alerting_api_helper.ts @@ -0,0 +1,117 @@ +/* + * 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 { SuperTest, Test } from 'supertest'; +import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +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'; +import { ApmApiClient } from '../../common/config'; + +export async function createIndexConnector({ + supertest, + name, + indexName, +}: { + supertest: SuperTest; + name: string; + indexName: string; +}) { + const { body } = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name, + config: { + index: indexName, + refresh: true, + }, + connector_type_id: '.index', + }); + return body.id as string; +} + +export async function createApmRule({ + supertest, + name, + ruleTypeId, + params, + actions = [], +}: { + supertest: SuperTest; + ruleTypeId: T; + name: string; + params: ApmRuleParamsType[T]; + actions?: any[]; +}) { + const { body } = await supertest + .post(`/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send({ + params, + consumer: 'apm', + schedule: { + interval: '1m', + }, + tags: ['apm'], + name, + rule_type_id: ruleTypeId, + actions, + }); + return body; +} + +function getTimerange() { + return { + start: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), + end: new Date(Date.now() + 5 * 60 * 1000).toISOString(), + }; +} + +export async function fetchServiceInventoryAlertCounts(apmApiClient: ApmApiClient) { + const timerange = getTimerange(); + const serviceInventoryResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + ...timerange, + environment: 'ENVIRONMENT_ALL', + kuery: '', + probability: 1, + documentType: ApmDocumentType.ServiceTransactionMetric, + rollupInterval: RollupInterval.SixtyMinutes, + }, + }, + }); + return serviceInventoryResponse.body.items.reduce>((acc, item) => { + return { ...acc, [item.serviceName]: item.alertsCount ?? 0 }; + }, {}); +} + +export async function fetchServiceTabAlertCount({ + apmApiClient, + serviceName, +}: { + apmApiClient: ApmApiClient; + serviceName: string; +}) { + const timerange = getTimerange(); + const alertsCountReponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/alerts_count', + params: { + path: { + serviceName, + }, + query: { + ...timerange, + environment: 'ENVIRONMENT_ALL', + }, + }, + }); + + return alertsCountReponse.body.alertsCount; +} 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 39c81e71948a0..a72760ca339ce 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 @@ -9,8 +9,10 @@ import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { range } from 'lodash'; +import { ANOMALY_SEVERITY } from '@kbn/apm-plugin/common/ml_constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createAndRunApmMlJobs } from '../../common/utils/create_and_run_apm_ml_jobs'; +import { createApmRule } from './alerting_api_helper'; import { waitForRuleStatus } from './wait_for_rule_status'; export default function ApiTest({ getService }: FtrProviderContext) { @@ -18,7 +20,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const ml = getService('ml'); - const log = getService('log'); const es = getService('es'); const synthtraceEsClient = getService('synthtraceEsClient'); @@ -81,36 +82,29 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('checks if alert is active', async () => { - const { body: createdRule } = await supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send({ - params: { - environment: 'production', - windowSize: 99, - windowUnit: 'y', - anomalySeverityType: 'warning', - }, - consumer: 'apm', - schedule: { - interval: '1m', - }, - tags: ['apm', 'service.name:service-a'], - name: 'Latency anomaly | service-a', - rule_type_id: ApmRuleType.Anomaly, - notify_when: 'onActiveAlert', - actions: [], - }); - - ruleId = createdRule.id; - - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', + const createdRule = await createApmRule({ supertest, - log, + name: 'Latency anomaly | service-a', + params: { + environment: 'production', + windowSize: 99, + windowUnit: 'y', + anomalySeverityType: ANOMALY_SEVERITY.WARNING, + }, + ruleTypeId: ApmRuleType.Anomaly, }); - expect(executionStatus.status).to.be('active'); + + ruleId = createdRule.id; + if (!ruleId) { + expect(ruleId).to.not.eql(undefined); + } else { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + } }); }); } 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 new file mode 100644 index 0000000000000..a19f1ef93503a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts @@ -0,0 +1,165 @@ +/* + * 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 { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { errorCountMessage } from '@kbn/apm-plugin/common/rules/default_action_message'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createApmRule, + createIndexConnector, + fetchServiceInventoryAlertCounts, + fetchServiceTabAlertCount, +} from './alerting_api_helper'; +import { waitForRuleStatus, waitForDocumentInIndex } from './wait_for_rule_status'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + + const supertest = getService('supertest'); + const es = getService('es'); + const apmApiClient = getService('apmApiClient'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + const synthtraceEsClient = getService('synthtraceEsClient'); + + registry.when('error count threshold alert', { config: 'basic', archives: [] }, () => { + let ruleId: string; + let actionId: string | undefined; + + const INDEX_NAME = 'error-count'; + + before(async () => { + const opbeansJava = apm + .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) + .instance('instance'); + const opbeansNode = apm + .service({ name: 'opbeans-node', environment: 'production', agentName: 'node' }) + .instance('instance'); + const events = timerange('now-15m', 'now') + .ratePerMinute(1) + .generator((timestamp) => { + return [ + opbeansJava + .transaction({ transactionName: 'tx-java' }) + .timestamp(timestamp) + .duration(100) + .failure() + .errors( + opbeansJava + .error({ message: '[ResponseError] index_not_found_exception' }) + .timestamp(timestamp + 50) + ), + opbeansNode + .transaction({ transactionName: 'tx-node' }) + .timestamp(timestamp) + .duration(100) + .success(), + ]; + }); + await synthtraceEsClient.index(events); + }); + + after(async () => { + await synthtraceEsClient.clean(); + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esDeleteAllIndices(['.alerts*', INDEX_NAME]); + await es.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'apm' } }, + }); + }); + + describe('create alert', () => { + before(async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Error count API test', + indexName: INDEX_NAME, + }); + const createdRule = await createApmRule({ + supertest, + ruleTypeId: ApmRuleType.ErrorCount, + name: 'Apm error count', + params: { + environment: 'production', + threshold: 1, + windowSize: 1, + windowUnit: 'h', + }, + actions: [ + { + group: 'threshold_met', + id: actionId, + params: { + documents: [{ message: errorCountMessage }], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + expect(createdRule.id).to.not.eql(undefined); + ruleId = createdRule.id; + }); + + it('checks if alert is active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('returns correct message', async () => { + const resp = await waitForDocumentInIndex<{ message: string }>({ + es, + indexName: INDEX_NAME, + }); + + expect(resp.hits.hits[0]._source?.message).eql( + `Apm error count alert is firing because of the following conditions: + +- Service name: opbeans-java +- Environment: production +- Threshold: 1 +- Triggered value: 15 errors over the last 1 hr` + ); + }); + + it('shows the correct alert count for each service on service inventory', async () => { + const serviceInventoryAlertCounts = await fetchServiceInventoryAlertCounts(apmApiClient); + expect(serviceInventoryAlertCounts).to.eql({ + 'opbeans-node': 0, + 'opbeans-java': 1, + }); + }); + + it('shows the correct alert count in opbeans-java service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-java', + }); + expect(serviceTabAlertCount).to.be(1); + }); + + it('shows the correct alert count in opbeans-node service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-node', + }); + expect(serviceTabAlertCount).to.be(0); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts index f4814358f1636..44da2bea36bf2 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts @@ -4,53 +4,52 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ToolingLog } from '@kbn/tooling-log'; -import expect from '@kbn/expect'; +import type { Client } from '@elastic/elasticsearch'; +import type { + AggregationsAggregate, + SearchResponse, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import pRetry from 'p-retry'; import type SuperTest from 'supertest'; -const WAIT_FOR_STATUS_INCREMENT = 500; - export async function waitForRuleStatus({ id, expectedStatus, - waitMillis = 10000, supertest, - log, }: { + id: string; expectedStatus: string; supertest: SuperTest.SuperTest; - log: ToolingLog; - waitMillis?: number; - id?: string; }): Promise> { - if (waitMillis < 0 || !id) { - expect().fail(`waiting for alert ${id} status ${expectedStatus} timed out`); - } - - const response = await supertest.get(`/api/alerting/rule/${id}`); - expect(response.status).to.eql(200); - - const { execution_status: executionStatus } = response.body || {}; - const { status } = executionStatus || {}; - - const message = `waitForStatus(${expectedStatus}): got ${JSON.stringify(executionStatus)}`; - - if (status === expectedStatus) { - return executionStatus; - } - - log.debug(`${message}, retrying`); - - await delay(WAIT_FOR_STATUS_INCREMENT); - return await waitForRuleStatus({ - id, - expectedStatus, - waitMillis: waitMillis - WAIT_FOR_STATUS_INCREMENT, - supertest, - log, - }); + return pRetry( + async () => { + const response = await supertest.get(`/api/alerting/rule/${id}`); + const { execution_status: executionStatus } = response.body || {}; + const { status } = executionStatus || {}; + if (status !== expectedStatus) { + throw new Error(`waitForStatus(${expectedStatus}): got ${status}`); + } + return executionStatus; + }, + { retries: 10 } + ); } -async function delay(millis: number): Promise { - await new Promise((resolve) => setTimeout(resolve, millis)); +export async function waitForDocumentInIndex({ + es, + indexName, +}: { + es: Client; + indexName: string; +}): Promise>> { + return pRetry( + async () => { + const response = await es.search({ index: indexName }); + if (response.hits.hits.length === 0) { + throw new Error('No hits found'); + } + return response; + }, + { retries: 10 } + ); } 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 69e3d7678ca9e..abae62f2012a6 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,10 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { waitForActiveAlert } from '../../../common/utils/wait_for_active_alert'; +import { createApmRule } from '../../alerts/alerting_api_helper'; import { createServiceGroupApi, deleteAllServiceGroups, @@ -26,28 +27,21 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = Date.now() - 24 * 60 * 60 * 1000; const end = Date.now(); - async function createRule() { - return supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'true') - .send({ - params: { - serviceName: 'synth-go', - transactionType: '', - windowSize: 99, - windowUnit: 'y', - threshold: 100, - aggregationType: 'avg', - environment: 'testing', - }, - consumer: 'apm', - schedule: { interval: '1m' }, - tags: ['apm'], - name: 'Latency threshold | synth-go', - rule_type_id: ApmRuleType.TransactionDuration, - notify_when: 'onActiveAlert', - actions: [], - }); + function createRule() { + return createApmRule({ + supertest, + name: 'Latency threshold | synth-go', + params: { + serviceName: 'synth-go', + transactionType: '', + windowSize: 99, + windowUnit: 'y', + threshold: 100, + aggregationType: AggregationType.Avg, + environment: 'testing', + }, + ruleTypeId: ApmRuleType.TransactionDuration, + }); } registry.when('Service group counts', { config: 'basic', archives: [] }, () => { @@ -89,7 +83,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('with alerts', () => { let ruleId: string; before(async () => { - const { body: createdRule } = await createRule(); + const createdRule = await createRule(); ruleId = createdRule.id; await waitForActiveAlert({ ruleId, esClient, log }); }); 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 a8c92dfdd256e..35ee8da8ba39b 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,10 +5,11 @@ * 2.0. */ import expect from '@kbn/expect'; -import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { waitForActiveAlert } from '../../common/utils/wait_for_active_alert'; +import { createApmRule } from '../alerts/alerting_api_helper'; export default function ServiceAlerts({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -42,28 +43,21 @@ export default function ServiceAlerts({ getService }: FtrProviderContext) { }); } - async function createRule() { - return supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'true') - .send({ - params: { - serviceName: goService, - transactionType: '', - windowSize: 99, - windowUnit: 'y', - threshold: 100, - aggregationType: 'avg', - environment: 'testing', - }, - consumer: 'apm', - schedule: { interval: '1m' }, - tags: ['apm'], - name: `Latency threshold | ${goService}`, - rule_type_id: ApmRuleType.TransactionDuration, - notify_when: 'onActiveAlert', - actions: [], - }); + function createRule() { + return createApmRule({ + supertest, + name: `Latency threshold | ${goService}`, + params: { + serviceName: goService, + transactionType: '', + windowSize: 99, + windowUnit: 'y', + threshold: 100, + aggregationType: AggregationType.Avg, + environment: 'testing', + }, + ruleTypeId: ApmRuleType.TransactionDuration, + }); } registry.when('Service alerts', { config: 'basic', archives: [] }, () => { @@ -121,7 +115,7 @@ export default function ServiceAlerts({ getService }: FtrProviderContext) { describe('with alerts', () => { let ruleId: string; before(async () => { - const { body: createdRule } = await createRule(); + const createdRule = await createRule(); ruleId = createdRule.id; await waitForActiveAlert({ ruleId, esClient, log }); }); From c6641c9b3a696699cda160b2273e14245e9eb1dc Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Mon, 13 Feb 2023 11:53:53 -0800 Subject: [PATCH 2/8] [RAM][Flapping] Convert rule flapping settings API to snake case (#150951) ## Summary Resolves: https://github.com/elastic/kibana/issues/150623 Convert the rule flapping settings API properties from camel case to snake case. The properties are rewritten once it hits the application code to camel case again. ### Checklist - [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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/get_flapping_settings.test.ts | 12 ++++- .../server/routes/get_flapping_settings.ts | 24 ++++++++-- .../routes/update_flapping_settings.test.ts | 37 ++++++++++----- .../server/routes/update_flapping_settings.ts | 46 ++++++++++++++++--- .../.storybook/context/http.ts | 4 +- .../lib/rule_api/get_flapping_settings.ts | 16 ++++++- .../lib/rule_api/update_flapping_settings.ts | 24 ++++++++-- .../common/lib/reset_rules_settings.ts | 6 ++- .../tests/alerting/get_flapping_settings.ts | 14 +++--- .../alerting/update_flapping_settings.ts | 40 ++++++++-------- .../tests/alerting/group1/event_log.ts | 8 ++-- .../triggers_actions_ui/rules_settings.ts | 4 +- 12 files changed, 173 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts b/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts index 156ab604fb905..80354da80b784 100644 --- a/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts @@ -58,6 +58,16 @@ describe('getFlappingSettingsRoute', () => { await handler(context, req, res); expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(1); - expect(res.ok).toHaveBeenCalled(); + expect(res.ok).toHaveBeenCalledWith({ + body: expect.objectContaining({ + enabled: true, + look_back_window: 10, + status_change_threshold: 10, + created_by: 'test name', + updated_by: 'test name', + created_at: expect.any(String), + updated_at: expect.any(String), + }), + }); }); }); diff --git a/x-pack/plugins/alerting/server/routes/get_flapping_settings.ts b/x-pack/plugins/alerting/server/routes/get_flapping_settings.ts index 6ae039032994d..5d4795d664ed5 100644 --- a/x-pack/plugins/alerting/server/routes/get_flapping_settings.ts +++ b/x-pack/plugins/alerting/server/routes/get_flapping_settings.ts @@ -8,8 +8,26 @@ import { IRouter } from '@kbn/core/server'; import { ILicenseState } from '../lib'; import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; -import { verifyAccessAndContext } from './lib'; -import { API_PRIVILEGES } from '../../common'; +import { verifyAccessAndContext, RewriteResponseCase } from './lib'; +import { API_PRIVILEGES, RulesSettingsFlapping } from '../../common'; + +const rewriteBodyRes: RewriteResponseCase = ({ + lookBackWindow, + statusChangeThreshold, + createdBy, + updatedBy, + createdAt, + updatedAt, + ...rest +}) => ({ + ...rest, + look_back_window: lookBackWindow, + status_change_threshold: statusChangeThreshold, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, +}); export const getFlappingSettingsRoute = ( router: IRouter, @@ -27,7 +45,7 @@ export const getFlappingSettingsRoute = ( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesSettingsClient = (await context.alerting).getRulesSettingsClient(); const flappingSettings = await rulesSettingsClient.flapping().get(); - return res.ok({ body: flappingSettings }); + return res.ok({ body: rewriteBodyRes(flappingSettings) }); }) ) ); diff --git a/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts b/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts index 28914e71e7dd3..84fb238b8509b 100644 --- a/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts @@ -22,6 +22,16 @@ beforeEach(() => { rulesSettingsClient = rulesSettingsClientMock.create(); }); +const mockFlappingSettings = { + enabled: true, + lookBackWindow: 10, + statusChangeThreshold: 10, + createdBy: 'test name', + updatedBy: 'test name', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), +}; + describe('updateFlappingSettingsRoute', () => { test('updates flapping settings', async () => { const licenseState = licenseStateMock.create(); @@ -40,20 +50,13 @@ describe('updateFlappingSettingsRoute', () => { } `); - (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValue({ - enabled: true, - lookBackWindow: 10, - statusChangeThreshold: 10, - createdBy: 'test name', - updatedBy: 'test name', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }); + (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValue(mockFlappingSettings); + (rulesSettingsClient.flapping().update as jest.Mock).mockResolvedValue(mockFlappingSettings); const updateResult = { enabled: false, - lookBackWindow: 6, - statusChangeThreshold: 5, + look_back_window: 6, + status_change_threshold: 5, }; const [context, req, res] = mockHandlerArguments( @@ -77,6 +80,16 @@ describe('updateFlappingSettingsRoute', () => { }, ] `); - expect(res.ok).toHaveBeenCalled(); + expect(res.ok).toHaveBeenCalledWith({ + body: expect.objectContaining({ + enabled: true, + look_back_window: 10, + status_change_threshold: 10, + created_by: 'test name', + updated_by: 'test name', + created_at: expect.any(String), + updated_at: expect.any(String), + }), + }); }); }); diff --git a/x-pack/plugins/alerting/server/routes/update_flapping_settings.ts b/x-pack/plugins/alerting/server/routes/update_flapping_settings.ts index ede33a7d36a95..6df16434d3833 100644 --- a/x-pack/plugins/alerting/server/routes/update_flapping_settings.ts +++ b/x-pack/plugins/alerting/server/routes/update_flapping_settings.ts @@ -8,14 +8,46 @@ import { IRouter } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../lib'; -import { verifyAccessAndContext } from './lib'; +import { verifyAccessAndContext, RewriteResponseCase, RewriteRequestCase } from './lib'; import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; -import { API_PRIVILEGES } from '../../common'; +import { + API_PRIVILEGES, + RulesSettingsFlapping, + RulesSettingsFlappingProperties, +} from '../../common'; const bodySchema = schema.object({ enabled: schema.boolean(), - lookBackWindow: schema.number(), - statusChangeThreshold: schema.number(), + look_back_window: schema.number(), + status_change_threshold: schema.number(), +}); + +const rewriteQueryReq: RewriteRequestCase = ({ + look_back_window: lookBackWindow, + status_change_threshold: statusChangeThreshold, + ...rest +}) => ({ + ...rest, + lookBackWindow, + statusChangeThreshold, +}); + +const rewriteBodyRes: RewriteResponseCase = ({ + lookBackWindow, + statusChangeThreshold, + createdBy, + updatedBy, + createdAt, + updatedAt, + ...rest +}) => ({ + ...rest, + look_back_window: lookBackWindow, + status_change_threshold: statusChangeThreshold, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, }); export const updateFlappingSettingsRoute = ( @@ -36,10 +68,12 @@ export const updateFlappingSettingsRoute = ( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesSettingsClient = (await context.alerting).getRulesSettingsClient(); - const updatedFlappingSettings = await rulesSettingsClient.flapping().update(req.body); + const updatedFlappingSettings = await rulesSettingsClient + .flapping() + .update(rewriteQueryReq(req.body)); return res.ok({ - body: updatedFlappingSettings, + body: updatedFlappingSettings && rewriteBodyRes(updatedFlappingSettings), }); }) ) diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts index 69e1cf2f64b91..ba7cc79901801 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts @@ -279,8 +279,8 @@ const rulesSettingsGetResponse = (path: string) => { if (path.endsWith('/settings/_flapping')) { return { enabled: true, - lookBackWindow: 20, - statusChangeThreshold: 4, + look_back_window: 20, + status_change_threshold: 4, }; } }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_flapping_settings.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_flapping_settings.ts index 68947de984fb4..931b1037ef729 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_flapping_settings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_flapping_settings.ts @@ -6,11 +6,23 @@ */ import { HttpSetup } from '@kbn/core/public'; +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; import { RulesSettingsFlapping } from '@kbn/alerting-plugin/common'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; -export const getFlappingSettings = ({ http }: { http: HttpSetup }) => { - return http.get( +const rewriteBodyRes: RewriteRequestCase = ({ + look_back_window: lookBackWindow, + status_change_threshold: statusChangeThreshold, + ...rest +}: any) => ({ + ...rest, + lookBackWindow, + statusChangeThreshold, +}); + +export const getFlappingSettings = async ({ http }: { http: HttpSetup }) => { + const res = await http.get>( `${INTERNAL_BASE_ALERTING_API_PATH}/rules/settings/_flapping` ); + return rewriteBodyRes(res); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_flapping_settings.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_flapping_settings.ts index f38393b591d72..9e03da7e6e100 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_flapping_settings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update_flapping_settings.ts @@ -10,9 +10,20 @@ import { RulesSettingsFlapping, RulesSettingsFlappingProperties, } from '@kbn/alerting-plugin/common'; +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; -export const updateFlappingSettings = ({ +const rewriteBodyRes: RewriteRequestCase = ({ + look_back_window: lookBackWindow, + status_change_threshold: statusChangeThreshold, + ...rest +}: any) => ({ + ...rest, + lookBackWindow, + statusChangeThreshold, +}); + +export const updateFlappingSettings = async ({ http, flappingSettings, }: { @@ -21,14 +32,21 @@ export const updateFlappingSettings = ({ }) => { let body: string; try { - body = JSON.stringify(flappingSettings); + body = JSON.stringify({ + enabled: flappingSettings.enabled, + look_back_window: flappingSettings.lookBackWindow, + status_change_threshold: flappingSettings.statusChangeThreshold, + }); } catch (e) { throw new Error(`Unable to parse flapping settings update params: ${e}`); } - return http.post( + + const res = await http.post>( `${INTERNAL_BASE_ALERTING_API_PATH}/rules/settings/_flapping`, { body, } ); + + return rewriteBodyRes(res); }; diff --git a/x-pack/test/alerting_api_integration/common/lib/reset_rules_settings.ts b/x-pack/test/alerting_api_integration/common/lib/reset_rules_settings.ts index b1b24856e9ef8..17ce4985e1f0c 100644 --- a/x-pack/test/alerting_api_integration/common/lib/reset_rules_settings.ts +++ b/x-pack/test/alerting_api_integration/common/lib/reset_rules_settings.ts @@ -14,6 +14,10 @@ export const resetRulesSettings = (supertest: any, space: string) => { .post(`${getUrlPrefix(space)}/internal/alerting/rules/settings/_flapping`) .set('kbn-xsrf', 'foo') .auth(Superuser.username, Superuser.password) - .send(DEFAULT_FLAPPING_SETTINGS) + .send({ + enabled: DEFAULT_FLAPPING_SETTINGS.enabled, + look_back_window: DEFAULT_FLAPPING_SETTINGS.lookBackWindow, + status_change_threshold: DEFAULT_FLAPPING_SETTINGS.statusChangeThreshold, + }) .expect(200); }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/get_flapping_settings.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/get_flapping_settings.ts index 7bc307f41e6d4..386e32b8e5778 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/get_flapping_settings.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/get_flapping_settings.ts @@ -51,14 +51,16 @@ export default function getFlappingSettingsTests({ getService }: FtrProviderCont case 'space_1_all at space1': expect(response.statusCode).to.eql(200); expect(response.body.enabled).to.eql(DEFAULT_FLAPPING_SETTINGS.enabled); - expect(response.body.lookBackWindow).to.eql(DEFAULT_FLAPPING_SETTINGS.lookBackWindow); - expect(response.body.statusChangeThreshold).to.eql( + expect(response.body.look_back_window).to.eql( + DEFAULT_FLAPPING_SETTINGS.lookBackWindow + ); + expect(response.body.status_change_threshold).to.eql( DEFAULT_FLAPPING_SETTINGS.statusChangeThreshold ); - expect(response.body.createdBy).to.be.a('string'); - expect(response.body.updatedBy).to.be.a('string'); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(response.body.created_by).to.be.a('string'); + expect(response.body.updated_by).to.be.a('string'); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/update_flapping_settings.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/update_flapping_settings.ts index 93659256d2e97..a4d39ad4be89b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/update_flapping_settings.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/update_flapping_settings.ts @@ -30,8 +30,8 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo .auth(user.username, user.password) .send({ enabled: false, - lookBackWindow: 20, - statusChangeThreshold: 20, + look_back_window: 20, + status_change_threshold: 20, }); switch (scenario.id) { @@ -51,12 +51,12 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo case 'space_1_all at space1': expect(response.statusCode).to.eql(200); expect(response.body.enabled).to.eql(false); - expect(response.body.lookBackWindow).to.eql(20); - expect(response.body.statusChangeThreshold).to.eql(20); - expect(response.body.createdBy).to.eql(user.username); - expect(response.body.updatedBy).to.eql(user.username); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(response.body.look_back_window).to.eql(20); + expect(response.body.status_change_threshold).to.eql(20); + expect(response.body.created_by).to.eql(user.username); + expect(response.body.updated_by).to.eql(user.username); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -72,8 +72,8 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo .auth(Superuser.username, Superuser.password) .send({ enabled: true, - lookBackWindow: 200, - statusChangeThreshold: 200, + look_back_window: 200, + status_change_threshold: 200, }) .expect(400); @@ -87,8 +87,8 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo .auth(Superuser.username, Superuser.password) .send({ enabled: true, - lookBackWindow: 20, - statusChangeThreshold: 200, + look_back_window: 20, + status_change_threshold: 200, }) .expect(400); @@ -102,8 +102,8 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo .auth(Superuser.username, Superuser.password) .send({ enabled: true, - lookBackWindow: 5, - statusChangeThreshold: 10, + look_back_window: 5, + status_change_threshold: 10, }) .expect(400); @@ -121,14 +121,14 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo .auth(Superuser.username, Superuser.password) .send({ enabled: false, - lookBackWindow: 20, - statusChangeThreshold: 20, + look_back_window: 20, + status_change_threshold: 20, }); expect(postResponse.statusCode).to.eql(200); expect(postResponse.body.enabled).to.eql(false); - expect(postResponse.body.lookBackWindow).to.eql(20); - expect(postResponse.body.statusChangeThreshold).to.eql(20); + expect(postResponse.body.look_back_window).to.eql(20); + expect(postResponse.body.status_change_threshold).to.eql(20); // Get the rules settings in space2 const getResponse = await supertestWithoutAuth @@ -137,8 +137,8 @@ export default function updateFlappingSettingsTest({ getService }: FtrProviderCo expect(getResponse.statusCode).to.eql(200); expect(getResponse.body.enabled).to.eql(true); - expect(getResponse.body.lookBackWindow).to.eql(20); - expect(getResponse.body.statusChangeThreshold).to.eql(4); + expect(getResponse.body.look_back_window).to.eql(20); + expect(getResponse.body.status_change_threshold).to.eql(4); }); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 5525631c2a534..6574c2164b06c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -546,8 +546,8 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .auth('superuser', 'superuser') .send({ enabled: true, - lookBackWindow: 3, - statusChangeThreshold: 2, + look_back_window: 3, + status_change_threshold: 2, }) .expect(200); const { body: createdAction } = await supertest @@ -630,8 +630,8 @@ export default function eventLogTests({ getService }: FtrProviderContext) { .auth('superuser', 'superuser') .send({ enabled: true, - lookBackWindow: 3, - statusChangeThreshold: 2, + look_back_window: 3, + status_change_threshold: 2, }) .expect(200); const { body: createdAction } = await supertest diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rules_settings.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rules_settings.ts index 6b4297f2dc153..56b6d08253ec4 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rules_settings.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rules_settings.ts @@ -47,8 +47,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { .set('kbn-xsrf', 'foo') .send({ enabled: true, - lookBackWindow: 10, - statusChangeThreshold: 10, + look_back_window: 10, + status_change_threshold: 10, }) .expect(200); }); From 6bd55dab203f4df8286cf3dc50bb1ee4a79419d9 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 13 Feb 2023 16:09:23 -0500 Subject: [PATCH 3/8] [Response Ops][Alerting] Adding ability for rule types to specify custom formatting for `getSummarizedAlerts` function (#150829) Resolves https://github.com/elastic/kibana/issues/150776 ## Summary As part of the [POC to onboard detection rules onto alert summaries](https://github.com/elastic/kibana/pull/147539/files), we uncovered a need to allow rule types to specify a custom format function for the alerts returned from the `getSummarizedAlerts` function. This will allow detection rules to perform some custom transformations before detection alerts are made available for notifications. This PR adds the necessary hook that can be used later on. --- .../create_get_summarized_alerts_fn.test.ts | 341 +++++++++++++++++- .../utils/create_get_summarized_alerts_fn.ts | 70 ++-- 2 files changed, 386 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.test.ts b/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.test.ts index 387bd0174dbc4..597d6f056fa15 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.test.ts @@ -9,6 +9,7 @@ import { RuleDataClientMock, } from '../rule_data_client/rule_data_client.mock'; import { + ALERT_ACTION_GROUP, ALERT_END, ALERT_INSTANCE_ID, ALERT_RULE_EXECUTION_UUID, @@ -18,7 +19,7 @@ import { EVENT_ACTION, TIMESTAMP, } from '../../common/technical_rule_data_field_names'; -import { createGetSummarizedAlertsFn } from './create_get_summarized_alerts_fn'; +import { AlertDocument, createGetSummarizedAlertsFn } from './create_get_summarized_alerts_fn'; describe('createGetSummarizedAlertsFn', () => { let ruleDataClientMock: RuleDataClientMock; @@ -1644,6 +1645,344 @@ describe('createGetSummarizedAlertsFn', () => { expect(summarizedAlerts.recovered.data).toEqual([]); }); + it('creates function that uses a custom format alerts function if defined', async () => { + ruleDataClientMock.getReader().search.mockResolvedValueOnce({ + hits: { + total: { + value: 6, + }, + hits: [ + { + _id: '1', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_3', + [ALERT_UUID]: 'uuid1', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_3', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid1', + }, + }, + }, + }, + { + _id: '2', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_4', + [ALERT_UUID]: 'uuid2', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_4', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid2', + }, + }, + }, + }, + { + _id: '3', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:10:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', + [ALERT_UUID]: 'uuid3', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_1', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid3', + }, + }, + }, + }, + { + _id: '4', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:20:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_2', + [ALERT_UUID]: 'uuid4', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_2', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid4', + }, + }, + }, + }, + { + _id: '5', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_5', + [ALERT_UUID]: 'uuid5', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_5', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid5', + }, + }, + }, + }, + { + _id: '6', + _index: '.alerts-default-000001', + _source: { + [TIMESTAMP]: '2020-01-01T12:20:00.000Z', + [ALERT_RULE_EXECUTION_UUID]: 'abc', + [ALERT_RULE_UUID]: 'rule-id', + [ALERT_INSTANCE_ID]: 'TEST_ALERT_9', + [ALERT_UUID]: 'uuid6', + kibana: { + alert: { + instance: { + id: 'TEST_ALERT_9', + }, + rule: { + execution: { + uuid: 'abc', + }, + }, + uuid: 'uuid6', + }, + }, + }, + }, + ], + }, + } as any); + const getSummarizedAlertsFn = createGetSummarizedAlertsFn({ + ruleDataClient: ruleDataClientMock, + useNamespace: true, + isLifecycleAlert: false, + formatAlert: (alert: AlertDocument) => { + return { + ...alert, + [ALERT_ACTION_GROUP]: 'boopboopdedoo', + }; + }, + })(); + + const summarizedAlerts = await getSummarizedAlertsFn({ + start: new Date('2020-01-01T11:00:00.000Z'), + end: new Date('2020-01-01T12:25:00.000Z'), + ruleId: 'rule-id', + spaceId: 'space-id', + excludedAlertInstanceIds: ['TEST_ALERT_10'], + }); + expect(ruleDataClientMock.getReader).toHaveBeenCalledWith({ namespace: 'space-id' }); + expect(ruleDataClientMock.getReader().search).toHaveBeenCalledTimes(1); + expect(ruleDataClientMock.getReader().search).toHaveBeenCalledWith({ + body: { + size: 100, + track_total_hits: true, + query: { + bool: { + filter: [ + { + range: { + [TIMESTAMP]: { + gte: '2020-01-01T11:00:00.000Z', + lt: '2020-01-01T12:25:00.000Z', + }, + }, + }, + { + term: { + [ALERT_RULE_UUID]: 'rule-id', + }, + }, + { + bool: { + must_not: { + terms: { + [ALERT_INSTANCE_ID]: ['TEST_ALERT_10'], + }, + }, + }, + }, + ], + }, + }, + }, + }); + expect(summarizedAlerts.new.count).toEqual(6); + expect(summarizedAlerts.ongoing.count).toEqual(0); + expect(summarizedAlerts.recovered.count).toEqual(0); + expect(summarizedAlerts.new.data).toEqual([ + { + _id: '1', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_3', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid1', + }, + }, + }, + { + _id: '2', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_4', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid2', + }, + }, + }, + { + _id: '3', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:10:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_1', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid3', + }, + }, + }, + { + _id: '4', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:20:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_2', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid4', + }, + }, + }, + { + _id: '5', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:00:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_5', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid5', + }, + }, + }, + { + _id: '6', + _index: '.alerts-default-000001', + [TIMESTAMP]: '2020-01-01T12:20:00.000Z', + kibana: { + alert: { + action_group: 'boopboopdedoo', + instance: { + id: 'TEST_ALERT_9', + }, + rule: { + execution: { + uuid: 'abc', + }, + uuid: 'rule-id', + }, + uuid: 'uuid6', + }, + }, + }, + ]); + expect(summarizedAlerts.ongoing.data).toEqual([]); + expect(summarizedAlerts.recovered.data).toEqual([]); + }); + it('throws error if search throws error', async () => { ruleDataClientMock.getReader().search.mockImplementation(() => { throw new Error('search error'); diff --git a/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.ts b/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.ts index 4754d47f236e3..95c971230317e 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.ts @@ -27,12 +27,13 @@ import { ParsedExperimentalFields } from '../../common/parse_experimental_fields import { IRuleDataClient, IRuleDataReader } from '../rule_data_client'; const MAX_ALERT_DOCS_TO_RETURN = 100; -type AlertDocument = Partial; +export type AlertDocument = Partial; interface CreateGetSummarizedAlertsFnOpts { ruleDataClient: PublicContract; useNamespace: boolean; isLifecycleAlert: boolean; + formatAlert?: (alert: AlertDocument) => AlertDocument; } export const createGetSummarizedAlertsFn = @@ -73,6 +74,7 @@ export const createGetSummarizedAlertsFn = ruleId, executionUuid: executionUuid!, isLifecycleAlert: opts.isLifecycleAlert, + formatAlert: opts.formatAlert, excludedAlertInstanceIds, }); } @@ -83,6 +85,7 @@ export const createGetSummarizedAlertsFn = start: start!, end: end!, isLifecycleAlert: opts.isLifecycleAlert, + formatAlert: opts.formatAlert, excludedAlertInstanceIds, }); }; @@ -93,6 +96,7 @@ interface GetAlertsByExecutionUuidOpts { ruleDataClientReader: IRuleDataReader; isLifecycleAlert: boolean; excludedAlertInstanceIds: string[]; + formatAlert?: (alert: AlertDocument) => AlertDocument; } const getAlertsByExecutionUuid = async ({ @@ -101,12 +105,14 @@ const getAlertsByExecutionUuid = async ({ ruleDataClientReader, isLifecycleAlert, excludedAlertInstanceIds, + formatAlert, }: GetAlertsByExecutionUuidOpts) => { if (isLifecycleAlert) { return getLifecycleAlertsByExecutionUuid({ executionUuid, ruleId, ruleDataClientReader, + formatAlert, excludedAlertInstanceIds, }); } @@ -115,6 +121,7 @@ const getAlertsByExecutionUuid = async ({ executionUuid, ruleId, ruleDataClientReader, + formatAlert, excludedAlertInstanceIds, }); }; @@ -124,6 +131,7 @@ interface GetAlertsByExecutionUuidHelperOpts { ruleId: string; ruleDataClientReader: IRuleDataReader; excludedAlertInstanceIds: string[]; + formatAlert?: (alert: AlertDocument) => AlertDocument; } const getPersistentAlertsByExecutionUuid = async ({ @@ -131,17 +139,15 @@ const getPersistentAlertsByExecutionUuid = async { // persistent alerts only create new alerts so query by execution UUID to // get all alerts created during an execution const request = getQueryByExecutionUuid(executionUuid, ruleId, excludedAlertInstanceIds); - const response = (await ruleDataClientReader.search(request)) as ESSearchResponse< - AlertDocument, - TSearchRequest - >; + const response = await doSearch(ruleDataClientReader, request, formatAlert); return { - new: getHitsWithCount(response), + new: response, ongoing: { count: 0, data: [], @@ -158,6 +164,7 @@ const getLifecycleAlertsByExecutionUuid = async ({ ruleId, ruleDataClientReader, excludedAlertInstanceIds, + formatAlert, }: GetAlertsByExecutionUuidHelperOpts) => { // lifecycle alerts assign a different action to an alert depending // on whether it is new/ongoing/recovered. query for each action in order @@ -170,13 +177,13 @@ const getLifecycleAlertsByExecutionUuid = async ({ ]; const responses = await Promise.all( - requests.map((request) => ruleDataClientReader.search(request)) + requests.map((request) => doSearch(ruleDataClientReader, request, formatAlert)) ); return { - new: getHitsWithCount(responses[0]), - ongoing: getHitsWithCount(responses[1]), - recovered: getHitsWithCount(responses[2]), + new: responses[0], + ongoing: responses[1], + recovered: responses[2], }; }; @@ -197,24 +204,35 @@ const expandFlattenedAlert = (alert: object) => { }; const getHitsWithCount = ( - response: ESSearchResponse + response: ESSearchResponse, + formatAlert?: (alert: AlertDocument) => AlertDocument ) => { return { count: (response.hits.total as SearchTotalHits).value, data: response.hits.hits.map((hit) => { const { _id, _index, _source } = hit; - const rawAlert = { + const formattedSource = formatAlert ? formatAlert(_source) : _source; + + const expandedSource = expandFlattenedAlert(formattedSource as object); + return { _id, _index, - ..._source, + ...expandedSource, }; - - return expandFlattenedAlert(rawAlert as object); }), }; }; +const doSearch = async ( + ruleDataClientReader: IRuleDataReader, + request: ESSearchRequest, + formatAlert?: (alert: AlertDocument) => AlertDocument +) => { + const response = await ruleDataClientReader.search(request); + return getHitsWithCount(response, formatAlert); +}; + const getQueryByExecutionUuid = ( executionUuid: string, ruleId: string, @@ -272,6 +290,7 @@ interface GetAlertsByTimeRangeOpts { ruleDataClientReader: IRuleDataReader; isLifecycleAlert: boolean; excludedAlertInstanceIds: string[]; + formatAlert?: (alert: AlertDocument) => AlertDocument; } const getAlertsByTimeRange = async ({ @@ -281,6 +300,7 @@ const getAlertsByTimeRange = async ({ ruleDataClientReader, isLifecycleAlert, excludedAlertInstanceIds, + formatAlert, }: GetAlertsByTimeRangeOpts) => { if (isLifecycleAlert) { return getLifecycleAlertsByTimeRange({ @@ -288,6 +308,7 @@ const getAlertsByTimeRange = async ({ end, ruleId, ruleDataClientReader, + formatAlert, excludedAlertInstanceIds, }); } @@ -297,6 +318,7 @@ const getAlertsByTimeRange = async ({ end, ruleId, ruleDataClientReader, + formatAlert, excludedAlertInstanceIds, }); }; @@ -306,6 +328,7 @@ interface GetAlertsByTimeRangeHelperOpts { end: Date; ruleId: string; ruleDataClientReader: IRuleDataReader; + formatAlert?: (alert: AlertDocument) => AlertDocument; excludedAlertInstanceIds: string[]; } @@ -320,18 +343,16 @@ const getPersistentAlertsByTimeRange = async { // persistent alerts only create new alerts so query for all alerts within the time // range and treat them as NEW const request = getQueryByTimeRange(start, end, ruleId, excludedAlertInstanceIds); - const response = (await ruleDataClientReader.search(request)) as ESSearchResponse< - AlertDocument, - TSearchRequest - >; + const response = await doSearch(ruleDataClientReader, request, formatAlert); return { - new: getHitsWithCount(response), + new: response, ongoing: { count: 0, data: [], @@ -348,6 +369,7 @@ const getLifecycleAlertsByTimeRange = async ({ end, ruleId, ruleDataClientReader, + formatAlert, excludedAlertInstanceIds, }: GetAlertsByTimeRangeHelperOpts) => { const requests = [ @@ -357,13 +379,13 @@ const getLifecycleAlertsByTimeRange = async ({ ]; const responses = await Promise.all( - requests.map((request) => ruleDataClientReader.search(request)) + requests.map((request) => doSearch(ruleDataClientReader, request, formatAlert)) ); return { - new: getHitsWithCount(responses[0]), - ongoing: getHitsWithCount(responses[1]), - recovered: getHitsWithCount(responses[2]), + new: responses[0], + ongoing: responses[1], + recovered: responses[2], }; }; From 457e13d962fdab47c8283a4b7e5c6a000ccf0bc6 Mon Sep 17 00:00:00 2001 From: JD Kurma Date: Mon, 13 Feb 2023 16:12:47 -0500 Subject: [PATCH 4/8] [Security Solution] Enrich Value List Telemetry (#149621) ## Summary Add cluster and license information to value list telemetry sent via security channel ### Checklist Delete any items that are not applicable to this PR. - [x] ~~Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)~~ - [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 - [x] ~~Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/))~~ - [x] ~~Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))~~ - [x] ~~If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~~ - [x] ~~This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))~~ - [x] ~~This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)~~ ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [x] ~~This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)~~ --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/telemetry/__mocks__/index.ts | 11 ++-- .../server/lib/telemetry/helpers.test.ts | 19 +++++-- .../server/lib/telemetry/helpers.ts | 54 ++++++++++--------- .../server/lib/telemetry/receiver.ts | 9 ++-- .../lib/telemetry/tasks/security_lists.ts | 15 +++++- .../server/lib/telemetry/types.ts | 9 +++- 6 files changed, 72 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index ced44ebfa0658..f5718895fff26 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -15,6 +15,7 @@ import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/packag import { stubEndpointAlertResponse, stubProcessTree, stubFetchTimelineEvents } from './timeline'; import { stubEndpointMetricsResponse } from './metrics'; import { prebuiltRuleAlertsResponse } from './prebuilt_rule_alerts'; +import type { ESClusterInfo, ESLicense } from '../types'; export const createMockTelemetryEventsSender = ( enableTelemetry?: boolean, @@ -37,8 +38,7 @@ export const createMockTelemetryEventsSender = ( } as unknown as jest.Mocked; }; -const stubClusterInfo = { - name: 'Stub-MacBook-Pro.local', +export const stubClusterInfo: ESClusterInfo = { cluster_name: 'elasticsearch', cluster_uuid: '5Pr5PXRQQpGJUTn0czAvKQ', version: { @@ -46,24 +46,23 @@ const stubClusterInfo = { build_type: 'tar', build_hash: '38537ab4a726b42ce8f034aad78d8fca4d4f3e51', build_date: moment().toISOString(), + build_flavor: 'DEFAULT', build_snapshot: true, lucene_version: '9.2.0', minimum_wire_compatibility_version: '7.17.0', minimum_index_compatibility_version: '7.0.0', }, - tagline: 'You Know, for Search', }; -const stubLicenseInfo = { +export const stubLicenseInfo: ESLicense = { status: 'active', uid: '4a7dde08-e5f8-4e50-80f8-bc85b72b4934', type: 'trial', issue_date: moment().toISOString(), issue_date_in_millis: 1653299879146, expiry_date: moment().toISOString(), - expiry_date_in_millis: 1655891879146, + expirty_date_in_millis: 1655891879146, max_nodes: 1000, - max_resource_units: null, issued_to: 'elasticsearch', issuer: 'elasticsearch', start_date_in_millis: -1, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index cba00773453fc..29a4158a77d00 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { createMockPackagePolicy } from './__mocks__'; +import { createMockPackagePolicy, stubClusterInfo, stubLicenseInfo } from './__mocks__'; import { LIST_DETECTION_RULE_EXCEPTION, LIST_ENDPOINT_EXCEPTION, @@ -21,7 +21,7 @@ import { isPackagePolicyList, templateExceptionList, addDefaultAdvancedPolicyConfigSettings, - metricsResponseToValueListMetaData, + formatValueListMetaData, tlog, setIsElasticCloudDeployment, createTaskMetric, @@ -805,10 +805,11 @@ describe('test advanced policy config overlap ', () => { describe('test metrics response to value list meta data', () => { test('can succeed when metrics response is fully populated', async () => { + jest.useFakeTimers().setSystemTime(new Date('2023-01-30')); const stubMetricResponses = { listMetricsResponse: { aggregations: { - total_value_list_count: 5, + total_value_list_count: { value: 5 }, type_breakdown: { buckets: [ { @@ -858,8 +859,12 @@ describe('test metrics response to value list meta data', () => { }, }, }; - const response = metricsResponseToValueListMetaData(stubMetricResponses); + const response = formatValueListMetaData(stubMetricResponses, stubClusterInfo, stubLicenseInfo); expect(response).toEqual({ + '@timestamp': '2023-01-30T00:00:00.000Z', + cluster_uuid: '5Pr5PXRQQpGJUTn0czAvKQ', + cluster_name: 'elasticsearch', + license_id: '4a7dde08-e5f8-4e50-80f8-bc85b72b4934', total_list_count: 5, types: [ { @@ -901,8 +906,12 @@ describe('test metrics response to value list meta data', () => { indicatorMatchMetricsResponse: {}, }; // @ts-ignore - const response = metricsResponseToValueListMetaData(stubMetricResponses); + const response = formatValueListMetaData(stubMetricResponses, stubClusterInfo, stubLicenseInfo); expect(response).toEqual({ + '@timestamp': '2023-01-30T00:00:00.000Z', + cluster_uuid: '5Pr5PXRQQpGJUTn0czAvKQ', + cluster_name: 'elasticsearch', + license_id: '4a7dde08-e5f8-4e50-80f8-bc85b72b4934', total_list_count: 0, types: [], lists: [], diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index 9b3a847b63e28..f03621899c800 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -18,10 +18,7 @@ import type { ESLicense, ListTemplate, TelemetryEvent, - ValueListResponseAggregation, - ValueListExceptionListResponseAggregation, - ValueListItemsResponseAggregation, - ValueListIndicatorMatchResponseAggregation, + ValueListResponse, TaskMetric, } from './types'; import { @@ -241,32 +238,37 @@ export const addDefaultAdvancedPolicyConfigSettings = (policyConfig: PolicyConfi return merge(DEFAULT_ADVANCED_POLICY_CONFIG_SETTINGS, policyConfig); }; -export const metricsResponseToValueListMetaData = ({ - listMetricsResponse, - itemMetricsResponse, - exceptionListMetricsResponse, - indicatorMatchMetricsResponse, -}: { - listMetricsResponse: ValueListResponseAggregation; - itemMetricsResponse: ValueListItemsResponseAggregation; - exceptionListMetricsResponse: ValueListExceptionListResponseAggregation; - indicatorMatchMetricsResponse: ValueListIndicatorMatchResponseAggregation; -}) => ({ - total_list_count: listMetricsResponse?.aggregations?.total_value_list_count ?? 0, +export const formatValueListMetaData = ( + valueListResponse: ValueListResponse, + clusterInfo: ESClusterInfo, + licenseInfo: ESLicense | undefined +) => ({ + '@timestamp': moment().toISOString(), + cluster_uuid: clusterInfo.cluster_uuid, + cluster_name: clusterInfo.cluster_name, + license_id: licenseInfo?.uid, + total_list_count: + valueListResponse.listMetricsResponse?.aggregations?.total_value_list_count?.value ?? 0, types: - listMetricsResponse?.aggregations?.type_breakdown?.buckets.map((breakdown) => ({ - type: breakdown.key, - count: breakdown.doc_count, - })) ?? [], + valueListResponse.listMetricsResponse?.aggregations?.type_breakdown?.buckets.map( + (breakdown) => ({ + type: breakdown.key, + count: breakdown.doc_count, + }) + ) ?? [], lists: - itemMetricsResponse?.aggregations?.value_list_item_count?.buckets.map((itemCount) => ({ - id: itemCount.key, - count: itemCount.doc_count, - })) ?? [], + valueListResponse.itemMetricsResponse?.aggregations?.value_list_item_count?.buckets.map( + (itemCount) => ({ + id: itemCount.key, + count: itemCount.doc_count, + }) + ) ?? [], included_in_exception_lists_count: - exceptionListMetricsResponse?.aggregations?.vl_included_in_exception_lists_count?.value ?? 0, + valueListResponse.exceptionListMetricsResponse?.aggregations + ?.vl_included_in_exception_lists_count?.value ?? 0, used_in_indicator_match_rule_count: - indicatorMatchMetricsResponse?.aggregations?.vl_used_in_indicator_match_rule_count?.value ?? 0, + valueListResponse.indicatorMatchMetricsResponse?.aggregations + ?.vl_used_in_indicator_match_rule_count?.value ?? 0, }); export let isElasticCloudDeployment = false; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index e8e0e62fac045..428dd82b4f430 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -41,7 +41,6 @@ import { exceptionListItemToTelemetryEntry, trustedApplicationToTelemetryEntry, ruleExceptionListItemToTelemetryEvent, - metricsResponseToValueListMetaData, tlog, } from './helpers'; import { Fetcher } from '../../endpoint/routes/resolver/tree/utils/fetch'; @@ -55,7 +54,7 @@ import type { GetEndpointListResponse, RuleSearchResult, ExceptionListItem, - ValueListMetaData, + ValueListResponse, ValueListResponseAggregation, ValueListItemsResponseAggregation, ValueListExceptionListResponseAggregation, @@ -172,7 +171,7 @@ export interface ITelemetryReceiver { nodeIds: string[] ): Promise>>; - fetchValueListMetaData(interval: number): Promise; + fetchValueListMetaData(interval: number): Promise; } export class TelemetryReceiver implements ITelemetryReceiver { @@ -924,12 +923,12 @@ export class TelemetryReceiver implements ITelemetryReceiver { exceptionListMetrics as unknown as ValueListExceptionListResponseAggregation; const indicatorMatchMetricsResponse = indicatorMatchMetrics as unknown as ValueListIndicatorMatchResponseAggregation; - return metricsResponseToValueListMetaData({ + return { listMetricsResponse, itemMetricsResponse, exceptionListMetricsResponse, indicatorMatchMetricsResponse, - }); + }; } public async fetchClusterInfo(): Promise { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index 08baef614c1b8..68a6fb643e1ba 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -18,7 +18,13 @@ import { TASK_METRICS_CHANNEL, } from '../constants'; import type { ESClusterInfo, ESLicense } from '../types'; -import { batchTelemetryRecords, templateExceptionList, tlog, createTaskMetric } from '../helpers'; +import { + batchTelemetryRecords, + templateExceptionList, + tlog, + createTaskMetric, + formatValueListMetaData, +} from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; @@ -114,9 +120,14 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) } // Value list meta data - const valueListMetaData = await receiver.fetchValueListMetaData( + const valueListResponse = await receiver.fetchValueListMetaData( FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS ); + const valueListMetaData = formatValueListMetaData( + valueListResponse, + clusterInfo, + licenseInfo + ); tlog(logger, `Value List Meta Data: ${JSON.stringify(valueListMetaData)}`); if (valueListMetaData?.total_list_count) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index 5566a118a6e90..ba61f6b85aaab 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -382,7 +382,7 @@ export interface ValueListMetaData { export interface ValueListResponseAggregation { aggregations: { - total_value_list_count: number; + total_value_list_count: { value: number }; type_breakdown: { buckets: Array<{ key: string; @@ -437,3 +437,10 @@ export interface TelemetryFilterListArtifact { exception_lists: AllowlistFields; prebuilt_rules_alerts: AllowlistFields; } + +export interface ValueListResponse { + listMetricsResponse: ValueListResponseAggregation; + itemMetricsResponse: ValueListItemsResponseAggregation; + exceptionListMetricsResponse: ValueListExceptionListResponseAggregation; + indicatorMatchMetricsResponse: ValueListIndicatorMatchResponseAggregation; +} From 17855ba5a240ce2cd60f318b49f0040d0cfabbbd Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 13 Feb 2023 15:48:28 -0600 Subject: [PATCH 5/8] [build] Fix config argument order (#151052) Snapshot builds are currently being marked as release builds in the config service. This impacts the release property in package.json snapshot builds and pull request deployments. This was introduced in https://github.com/elastic/kibana/commit/1b8581540295fde746dae6b4a09d74fb5821bfef#diff-dbea24da2a777429d025c1da037dd966d65bff2c97a7b78b82a33532f3ad06d9R63 --- src/dev/build/lib/config.test.ts | 18 ++++++++++++++++-- src/dev/build/lib/config.ts | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/dev/build/lib/config.test.ts b/src/dev/build/lib/config.test.ts index e46a1984ceac0..5f530a7f6f2d8 100644 --- a/src/dev/build/lib/config.test.ts +++ b/src/dev/build/lib/config.test.ts @@ -25,9 +25,12 @@ const versionInfo = jest.requireMock('./version_info').getVersionInfo(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); -const setup = async ({ targetAllPlatforms = true }: { targetAllPlatforms?: boolean } = {}) => { +const setup = async ({ + targetAllPlatforms = true, + isRelease = true, +}: { targetAllPlatforms?: boolean; isRelease?: boolean } = {}) => { return await Config.create({ - isRelease: true, + isRelease, targetAllPlatforms, dockerContextUseLocalArtifact: false, dockerCrossCompile: false, @@ -192,6 +195,17 @@ describe('#getBuildSha()', () => { }); }); +describe('#isRelease()', () => { + it('returns true when marked as a release', async () => { + const config = await setup({ isRelease: true }); + expect(config.isRelease).toBe(true); + }); + it('returns false when not marked as a release', async () => { + const config = await setup({ isRelease: false }); + expect(config.isRelease).toBe(false); + }); +}); + describe('#resolveFromTarget()', () => { it('resolves a relative path, from the target directory', async () => { const config = await setup(); diff --git a/src/dev/build/lib/config.ts b/src/dev/build/lib/config.ts index 42e772646fb36..175a3768924ab 100644 --- a/src/dev/build/lib/config.ts +++ b/src/dev/build/lib/config.ts @@ -82,8 +82,8 @@ export class Config { private readonly dockerTag: string | null, private readonly dockerTagQualifier: string | null, private readonly dockerPush: boolean, - public readonly downloadFreshNode: boolean, public readonly isRelease: boolean, + public readonly downloadFreshNode: boolean, public readonly pluginSelector: PluginSelector ) { this.pluginFilter = getPluginPackagesFilter(this.pluginSelector); From f1046e55c17685781110a11d2842afcbd6a2efa4 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 13 Feb 2023 15:50:45 -0600 Subject: [PATCH 6/8] [artifacts/container image] Only trigger on main branch (#151050) We only need to product container images for the most recent commit. This updates the trigger to only run on `main`. note: `skip-ci` label, this only runs on-merge and is a no-op from pull requests. --- .buildkite/scripts/steps/artifacts/docker_image_trigger.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/artifacts/docker_image_trigger.sh b/.buildkite/scripts/steps/artifacts/docker_image_trigger.sh index f76eee8986c40..0eb268e3f95f8 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image_trigger.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image_trigger.sh @@ -2,4 +2,7 @@ set -euo pipefail -ts-node .buildkite/scripts/steps/trigger_pipeline.ts kibana-artifacts-container-image "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT" +if [[ "$BUILDKITE_BRANCH" == "main" ]]; then + echo "--- Trigger artifacts container image pipeline" + ts-node .buildkite/scripts/steps/trigger_pipeline.ts kibana-artifacts-container-image "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT" +fi From 7d8ccd6577ea271bd053a382909a34fe5d8d3ac9 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:07:02 -0800 Subject: [PATCH 7/8] [RAM] Reduce triggers actions UI bundle size by removing unused exports (#150971) ## Summary Resolves: https://github.com/elastic/kibana/issues/151055 Reduces the triggers actions UI bundle size by about half, by removing unused exports from index files as those get bundled in the synchronous bundle even if the components importing them are lazy-loaded. ![new_bundle_size](https://user-images.githubusercontent.com/74562234/218380222-60b39a2a-dc47-4c8f-ba35-99b658b540e4.png) ### Checklist - [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 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/health_check.tsx | 2 +- .../rules_settings_link.test.tsx | 4 +- .../hooks/use_fetch_rule_action_connectors.ts | 3 +- .../hooks/use_get_flapping_settings.ts | 2 +- .../application/hooks/use_load_rule_types.ts | 2 +- .../hooks/use_load_rule_types_query.ts | 2 +- .../application/hooks/use_load_tags.test.tsx | 4 +- .../application/hooks/use_load_tags_query.ts | 2 +- .../hooks/use_update_flapping_settings.ts | 2 +- .../load_execution_kpi_aggregations.ts | 2 +- .../load_execution_log_aggregations.ts | 2 +- .../public/application/lib/rule_api/index.ts | 32 ------ .../public/application/lib/run_rule.test.ts | 4 +- .../public/application/lib/run_rule.ts | 2 +- .../with_bulk_rule_api_operations.test.tsx | 108 +++++++++++++----- .../with_bulk_rule_api_operations.tsx | 51 ++++----- .../components/rule_details.test.tsx | 6 +- .../rule_details/components/rule_details.tsx | 2 +- .../sections/rule_form/rule_add.test.tsx | 11 +- .../sections/rule_form/rule_add.tsx | 3 +- .../sections/rule_form/rule_edit.test.tsx | 8 +- .../sections/rule_form/rule_edit.tsx | 3 +- .../notify_badge/notify_badge_with_api.tsx | 8 +- .../components/rule_snooze_modal.tsx | 3 +- .../rules_list/components/rules_list.test.tsx | 29 ++++- .../rules_list/components/rules_list.tsx | 18 +-- .../rules_list_bulk_delete.test.tsx | 17 ++- .../rules_list_bulk_disable.test.tsx | 17 ++- .../components/rules_list_bulk_edit.test.tsx | 24 +++- .../rules_list_bulk_enable.test.tsx | 17 ++- .../triggers_actions_ui/public/index.ts | 11 +- 31 files changed, 248 insertions(+), 153 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index fa5c6656d5082..c274bee5eb865 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -21,7 +21,7 @@ import { useHealthContext } from '../context/health_context'; import { useKibana } from '../../common/lib/kibana'; import { CenterJustifiedSpinner } from './center_justified_spinner'; import { triggersActionsUiHealth } from '../../common/lib/health_api'; -import { alertingFrameworkHealth } from '../lib/rule_api'; +import { alertingFrameworkHealth } from '../lib/rule_api/health'; interface Props { inFlyout?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_link.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_link.test.tsx index e2e454e644ac0..17cc4ea8bca8f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_link.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/rules_setting/rules_settings_link.test.tsx @@ -14,8 +14,8 @@ import { coreMock } from '@kbn/core/public/mocks'; import { RulesSettingsFlapping } from '@kbn/alerting-plugin/common'; import { RulesSettingsLink } from './rules_settings_link'; import { useKibana } from '../../../common/lib/kibana'; -import { getFlappingSettings } from '../../lib/rule_api'; -import { updateFlappingSettings } from '../../lib/rule_api'; +import { getFlappingSettings } from '../../lib/rule_api/get_flapping_settings'; +import { updateFlappingSettings } from '../../lib/rule_api/update_flapping_settings'; jest.mock('../../../common/lib/kibana'); jest.mock('../../lib/rule_api/get_flapping_settings', () => ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts index c9e1bde1ae46a..039fd963137f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts @@ -7,7 +7,8 @@ import { useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { ActionConnector, loadAllActions } from '../..'; +import type { ActionConnector } from '../..'; +import { loadAllActions } from '../lib/action_connector_api'; import { useKibana } from '../../common/lib/kibana'; const ACTIONS_LOAD_ERROR = (errorMessage: string) => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_flapping_settings.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_flapping_settings.ts index 23f4af9e9daa7..e3166999df9ed 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_flapping_settings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_flapping_settings.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { useQuery } from '@tanstack/react-query'; import { RulesSettingsFlapping } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../common/lib/kibana'; -import { getFlappingSettings } from '../lib/rule_api'; +import { getFlappingSettings } from '../lib/rule_api/get_flapping_settings'; interface UseGetFlappingSettingsProps { enabled: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types.ts index 7382f3fbd39f2..bce0b9ec4ff66 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types.ts @@ -5,7 +5,7 @@ * 2.0. */ import { useEffect, useState, useRef } from 'react'; -import { loadRuleTypes } from '../lib/rule_api'; +import { loadRuleTypes } from '../lib/rule_api/rule_types'; import { RuleType, RuleTypeIndex } from '../../types'; import { useKibana } from '../../common/lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types_query.ts index 0b899b80e8882..d9b1f6080c18f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_types_query.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { useQuery } from '@tanstack/react-query'; import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; -import { loadRuleTypes } from '../lib/rule_api'; +import { loadRuleTypes } from '../lib/rule_api/rule_types'; import { useKibana } from '../../common/lib/kibana'; import { RuleType, RuleTypeIndex } from '../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags.test.tsx index f20f087741ab9..b8704eea881a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags.test.tsx @@ -15,12 +15,12 @@ import { waitFor } from '@testing-library/dom'; const MOCK_TAGS = ['a', 'b', 'c']; jest.mock('../../common/lib/kibana'); -jest.mock('../lib/rule_api', () => ({ +jest.mock('../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), })); const useKibanaMock = useKibana as jest.Mocked; -const { loadRuleTags } = jest.requireMock('../lib/rule_api'); +const { loadRuleTags } = jest.requireMock('../lib/rule_api/aggregate'); const queryClient = new QueryClient({ defaultOptions: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.ts index 8b4bee5deccc0..4e97c0bc52bac 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { useQuery } from '@tanstack/react-query'; -import { loadRuleTags } from '../lib/rule_api'; +import { loadRuleTags } from '../lib/rule_api/aggregate'; import { useKibana } from '../../common/lib/kibana'; interface UseLoadTagsQueryProps { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_flapping_settings.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_flapping_settings.ts index d5f978db9d3c0..ee1309a3f6582 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_flapping_settings.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_flapping_settings.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { useMutation } from '@tanstack/react-query'; import { RulesSettingsFlappingProperties } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../common/lib/kibana'; -import { updateFlappingSettings } from '../lib/rule_api'; +import { updateFlappingSettings } from '../lib/rule_api/update_flapping_settings'; interface UseUpdateFlappingSettingsProps { onClose: () => void; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_kpi_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_kpi_aggregations.ts index 34e4fca191673..c7b895b576c3f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_kpi_aggregations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_kpi_aggregations.ts @@ -8,7 +8,7 @@ import { HttpSetup } from '@kbn/core/public'; import { IExecutionKPIResult } from '@kbn/actions-plugin/common'; import { INTERNAL_BASE_ACTION_API_PATH } from '../../constants'; -import { getFilter } from '../rule_api'; +import { getFilter } from '../rule_api/get_filter'; export interface LoadGlobalConnectorExecutionKPIAggregationsProps { outcomeFilter?: string[]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_log_aggregations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_log_aggregations.ts index 210fe3ed310b3..76a96fe300359 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_log_aggregations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api/load_execution_log_aggregations.ts @@ -17,7 +17,7 @@ import { INTERNAL_BASE_ACTION_API_PATH, RewriteRequestCase, } from '@kbn/actions-plugin/common'; -import { getFilter } from '../rule_api'; +import { getFilter } from '../rule_api/get_filter'; const getRenamedLog = (data: IExecutionLog) => { const { duration_ms, schedule_delay_ms, ...rest } = data; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts index 75fc27e86e773..065272682516a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts @@ -5,47 +5,15 @@ * 2.0. */ -export { alertingFrameworkHealth } from './health'; export type { LoadRuleAggregationsProps } from './aggregate_helpers'; -export { loadRuleAggregations, loadRuleTags } from './aggregate'; -export { createRule } from './create'; -export { cloneRule } from './clone'; -export { loadRule } from './get_rule'; -export { loadRuleSummary } from './rule_summary'; -export { muteAlertInstance } from './mute_alert'; -export { muteRule, muteRules } from './mute'; -export { loadRuleTypes } from './rule_types'; export type { LoadRulesProps } from './rules_helpers'; -export { loadRules } from './rules'; -export { loadRuleState } from './state'; export type { LoadExecutionLogAggregationsProps, LoadGlobalExecutionLogAggregationsProps, } from './load_execution_log_aggregations'; -export { - loadExecutionLogAggregations, - loadGlobalExecutionLogAggregations, -} from './load_execution_log_aggregations'; export type { LoadExecutionKPIAggregationsProps } from './load_execution_kpi_aggregations'; -export { loadExecutionKPIAggregations } from './load_execution_kpi_aggregations'; export type { LoadGlobalExecutionKPIAggregationsProps } from './load_global_execution_kpi_aggregations'; -export { loadGlobalExecutionKPIAggregations } from './load_global_execution_kpi_aggregations'; export type { LoadActionErrorLogProps } from './load_action_error_log'; -export { loadActionErrorLog } from './load_action_error_log'; -export { unmuteAlertInstance } from './unmute_alert'; -export { unmuteRule, unmuteRules } from './unmute'; -export { updateRule } from './update'; -export { resolveRule } from './resolve_rule'; export type { BulkSnoozeRulesProps } from './snooze'; -export { snoozeRule, bulkSnoozeRules } from './snooze'; export type { BulkUnsnoozeRulesProps } from './unsnooze'; -export { unsnoozeRule, bulkUnsnoozeRules } from './unsnooze'; export type { BulkUpdateAPIKeyProps } from './update_api_key'; -export { updateAPIKey, bulkUpdateAPIKey } from './update_api_key'; -export { runSoon } from './run_soon'; -export { bulkDeleteRules } from './bulk_delete'; -export { bulkEnableRules } from './bulk_enable'; -export { bulkDisableRules } from './bulk_disable'; -export { getFilter } from './get_filter'; -export { getFlappingSettings } from './get_flapping_settings'; -export { updateFlappingSettings } from './update_flapping_settings'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.test.ts index ee9a391521fc7..1a33a30faf25d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.test.ts @@ -8,10 +8,10 @@ import { coreMock } from '@kbn/core/public/mocks'; import { runRule } from './run_rule'; -jest.mock('./rule_api', () => ({ +jest.mock('./rule_api/run_soon', () => ({ runSoon: jest.fn(), })); -const mockRunSoon = jest.requireMock('./rule_api').runSoon; +const { runSoon: mockRunSoon } = jest.requireMock('./rule_api/run_soon'); describe('runRule', () => { const mockCoreSetup = coreMock.createSetup(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.ts index 75617a0e45111..7aa425d10e9eb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/run_rule.ts @@ -7,7 +7,7 @@ import { HttpSetup } from '@kbn/core-http-browser'; import { IToasts } from '@kbn/core-notifications-browser'; import { i18n } from '@kbn/i18n'; -import { runSoon } from './rule_api'; +import { runSoon } from './rule_api/run_soon'; export async function runRule(http: HttpSetup, toasts: IToasts, id: string) { try { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx index 280e7f140f78f..753b3c8497fe9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.test.tsx @@ -5,18 +5,64 @@ * 2.0. */ +/* eslint-disable @typescript-eslint/no-shadow */ + import * as React from 'react'; import { shallow, mount } from 'enzyme'; import { v4 as uuidv4 } from 'uuid'; import { withBulkRuleOperations, ComponentOpts } from './with_bulk_rule_api_operations'; -import * as ruleApi from '../../../lib/rule_api'; import { SortField } from '../../../lib/rule_api/load_execution_log_aggregations'; - import { Rule } from '../../../../types'; import { useKibana } from '../../../../common/lib/kibana'; + jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../lib/rule_api'); +jest.mock('../../../lib/rule_api/load_execution_log_aggregations', () => ({ + loadExecutionLogAggregations: jest.fn(), +})); +jest.mock('../../../lib/rule_api/mute', () => ({ + muteRules: jest.fn(), + muteRule: jest.fn(), +})); +jest.mock('../../../lib/rule_api/unmute', () => ({ + unmuteRules: jest.fn(), + unmuteRule: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_delete', () => ({ + bulkDeleteRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_enable', () => ({ + bulkEnableRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_disable', () => ({ + bulkDisableRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/get_rule', () => ({ + loadRule: jest.fn(), +})); +jest.mock('../../../lib/rule_api/resolve_rule', () => ({ + resolveRule: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ + loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/load_action_error_log', () => ({ + loadActionErrorLog: jest.fn(), +})); + +const { loadExecutionLogAggregations } = jest.requireMock( + '../../../lib/rule_api/load_execution_log_aggregations' +); +const { muteRules, muteRule } = jest.requireMock('../../../lib/rule_api/mute'); +const { unmuteRules, unmuteRule } = jest.requireMock('../../../lib/rule_api/unmute'); +const { bulkDeleteRules } = jest.requireMock('../../../lib/rule_api/bulk_delete'); +const { bulkEnableRules } = jest.requireMock('../../../lib/rule_api/bulk_enable'); +const { bulkDisableRules } = jest.requireMock('../../../lib/rule_api/bulk_disable'); +const { loadRule } = jest.requireMock('../../../lib/rule_api/get_rule'); +const { resolveRule } = jest.requireMock('../../../lib/rule_api/resolve_rule'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { loadActionErrorLog } = jest.requireMock('../../../lib/rule_api/load_action_error_log'); + const useKibanaMock = useKibana as jest.Mocked; describe('with_bulk_rule_api_operations', () => { @@ -56,8 +102,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.muteRule).toHaveBeenCalledTimes(1); - expect(ruleApi.muteRule).toHaveBeenCalledWith({ id: rule.id, http }); + expect(muteRule).toHaveBeenCalledTimes(1); + expect(muteRule).toHaveBeenCalledWith({ id: rule.id, http }); }); it('unmuteRule calls the unmuteRule api', () => { @@ -71,8 +117,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.unmuteRule).toHaveBeenCalledTimes(1); - expect(ruleApi.unmuteRule).toHaveBeenCalledWith({ id: rule.id, http }); + expect(unmuteRule).toHaveBeenCalledTimes(1); + expect(unmuteRule).toHaveBeenCalledWith({ id: rule.id, http }); }); it('enableRule calls the muteRules api', () => { @@ -86,8 +132,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.bulkEnableRules).toHaveBeenCalledTimes(1); - expect(ruleApi.bulkEnableRules).toHaveBeenCalledWith({ ids: [rule.id], http }); + expect(bulkEnableRules).toHaveBeenCalledTimes(1); + expect(bulkEnableRules).toHaveBeenCalledWith({ ids: [rule.id], http }); }); it('disableRule calls the disableRule api', () => { @@ -101,8 +147,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.bulkDisableRules).toHaveBeenCalledTimes(1); - expect(ruleApi.bulkDisableRules).toHaveBeenCalledWith({ ids: [rule.id], http }); + expect(bulkDisableRules).toHaveBeenCalledTimes(1); + expect(bulkDisableRules).toHaveBeenCalledWith({ ids: [rule.id], http }); }); // bulk rules @@ -117,8 +163,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.muteRules).toHaveBeenCalledTimes(1); - expect(ruleApi.muteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); + expect(muteRules).toHaveBeenCalledTimes(1); + expect(muteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); }); it('unmuteRules calls the unmuteRules api', () => { @@ -132,8 +178,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.unmuteRules).toHaveBeenCalledTimes(1); - expect(ruleApi.unmuteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); + expect(unmuteRules).toHaveBeenCalledTimes(1); + expect(unmuteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); }); it('enableRules calls the muteRuless api', () => { @@ -155,8 +201,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.bulkEnableRules).toHaveBeenCalledTimes(1); - expect(ruleApi.bulkEnableRules).toHaveBeenCalledWith({ + expect(bulkEnableRules).toHaveBeenCalledTimes(1); + expect(bulkEnableRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id, rules[2].id], http, }); @@ -177,8 +223,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.bulkDisableRules).toHaveBeenCalledTimes(1); - expect(ruleApi.bulkDisableRules).toHaveBeenCalledWith({ + expect(bulkDisableRules).toHaveBeenCalledTimes(1); + expect(bulkDisableRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http, }); @@ -199,8 +245,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.bulkDeleteRules).toHaveBeenCalledTimes(1); - expect(ruleApi.bulkDeleteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); + expect(bulkDeleteRules).toHaveBeenCalledTimes(1); + expect(bulkDeleteRules).toHaveBeenCalledWith({ ids: [rules[0].id, rules[1].id], http }); }); it('loadRule calls the loadRule api', () => { @@ -214,8 +260,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.loadRule).toHaveBeenCalledTimes(1); - expect(ruleApi.loadRule).toHaveBeenCalledWith({ ruleId, http }); + expect(loadRule).toHaveBeenCalledTimes(1); + expect(loadRule).toHaveBeenCalledWith({ ruleId, http }); }); it('resolveRule calls the resolveRule api', () => { @@ -229,8 +275,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.resolveRule).toHaveBeenCalledTimes(1); - expect(ruleApi.resolveRule).toHaveBeenCalledWith({ ruleId, http }); + expect(resolveRule).toHaveBeenCalledTimes(1); + expect(resolveRule).toHaveBeenCalledWith({ ruleId, http }); }); it('loadRuleTypes calls the loadRuleTypes api', () => { @@ -243,8 +289,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.loadRuleTypes).toHaveBeenCalledTimes(1); - expect(ruleApi.loadRuleTypes).toHaveBeenCalledWith({ http }); + expect(loadRuleTypes).toHaveBeenCalledTimes(1); + expect(loadRuleTypes).toHaveBeenCalledWith({ http }); }); it('loadExecutionLogAggregations calls the loadExecutionLogAggregations API', () => { @@ -274,8 +320,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.loadExecutionLogAggregations).toHaveBeenCalledTimes(1); - expect(ruleApi.loadExecutionLogAggregations).toHaveBeenCalledWith({ + expect(loadExecutionLogAggregations).toHaveBeenCalledTimes(1); + expect(loadExecutionLogAggregations).toHaveBeenCalledWith({ ...callProps, http, }); @@ -307,8 +353,8 @@ describe('with_bulk_rule_api_operations', () => { const component = mount(); component.find('button').simulate('click'); - expect(ruleApi.loadActionErrorLog).toHaveBeenCalledTimes(1); - expect(ruleApi.loadActionErrorLog).toHaveBeenCalledWith({ + expect(loadActionErrorLog).toHaveBeenCalledTimes(1); + expect(loadActionErrorLog).toHaveBeenCalledWith({ ...callProps, http, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index 4fe559e090550..ecb431ad3ba93 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -24,40 +24,39 @@ import { BulkOperationResponse, BulkOperationAttributesWithoutHttp, } from '../../../../types'; -import { - muteRules, - unmuteRules, - muteRule, - unmuteRule, - muteAlertInstance, - unmuteAlertInstance, - loadRule, - loadRuleState, - loadRuleSummary, - loadRuleTypes, - alertingFrameworkHealth, - resolveRule, - loadExecutionLogAggregations, - loadGlobalExecutionLogAggregations, +import type { LoadExecutionLogAggregationsProps, LoadGlobalExecutionLogAggregationsProps, - loadActionErrorLog, LoadActionErrorLogProps, - snoozeRule, - bulkSnoozeRules, BulkSnoozeRulesProps, - unsnoozeRule, - loadExecutionKPIAggregations, LoadExecutionKPIAggregationsProps, - loadGlobalExecutionKPIAggregations, LoadGlobalExecutionKPIAggregationsProps, - bulkUnsnoozeRules, BulkUnsnoozeRulesProps, - cloneRule, - bulkDeleteRules, - bulkEnableRules, - bulkDisableRules, } from '../../../lib/rule_api'; +import { alertingFrameworkHealth } from '../../../lib/rule_api/health'; +import { cloneRule } from '../../../lib/rule_api/clone'; +import { loadRule } from '../../../lib/rule_api/get_rule'; +import { loadRuleSummary } from '../../../lib/rule_api/rule_summary'; +import { muteAlertInstance } from '../../../lib/rule_api/mute_alert'; +import { loadRuleTypes } from '../../../lib/rule_api/rule_types'; +import { + loadExecutionLogAggregations, + loadGlobalExecutionLogAggregations, +} from '../../../lib/rule_api/load_execution_log_aggregations'; +import { muteRules, muteRule } from '../../../lib/rule_api/mute'; +import { unmuteRules, unmuteRule } from '../../../lib/rule_api/unmute'; +import { loadRuleState } from '../../../lib/rule_api/state'; +import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_execution_kpi_aggregations'; +import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; +import { loadActionErrorLog } from '../../../lib/rule_api/load_action_error_log'; +import { unmuteAlertInstance } from '../../../lib/rule_api/unmute_alert'; +import { resolveRule } from '../../../lib/rule_api/resolve_rule'; +import { snoozeRule, bulkSnoozeRules } from '../../../lib/rule_api/snooze'; +import { unsnoozeRule, bulkUnsnoozeRules } from '../../../lib/rule_api/unsnooze'; +import { bulkDeleteRules } from '../../../lib/rule_api/bulk_delete'; +import { bulkEnableRules } from '../../../lib/rule_api/bulk_enable'; +import { bulkDisableRules } from '../../../lib/rule_api/bulk_disable'; + import { useKibana } from '../../../../common/lib/kibana'; export interface ComponentOpts { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index 4318412fe5261..a57b896a95902 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -44,11 +44,11 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn().mockResolvedValue([]), })); -jest.mock('../../../lib/rule_api', () => ({ +jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), - deleteRules: jest.fn(), })); -const { bulkUpdateAPIKey } = jest.requireMock('../../../lib/rule_api'); + +const { bulkUpdateAPIKey } = jest.requireMock('../../../lib/rule_api/update_api_key'); jest.mock('../../../lib/capabilities', () => ({ hasAllPrivilege: jest.fn(() => true), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index 0e06036b2b32e..acc37089f4b7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -27,7 +27,7 @@ import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; -import { bulkUpdateAPIKey } from '../../../lib/rule_api'; +import { bulkUpdateAPIKey } from '../../../lib/rule_api/update_api_key'; import { RulesDeleteModalConfirmation } from '../../../components/rules_delete_modal_confirmation'; import { RuleActionsPopover } from './rule_actions_popover'; import { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx index 5467dc38c35fb..d3e989bd135a0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx @@ -13,7 +13,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '@kbn/core/public/mocks'; import RuleAdd from './rule_add'; -import { createRule, alertingFrameworkHealth } from '../../lib/rule_api'; +import { createRule } from '../../lib/rule_api/create'; +import { alertingFrameworkHealth } from '../../lib/rule_api/health'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { Rule, @@ -32,9 +33,13 @@ import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api' jest.mock('../../../common/lib/kibana'); -jest.mock('../../lib/rule_api', () => ({ +jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../lib/rule_api/create', () => ({ createRule: jest.fn(), +})); +jest.mock('../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -88,7 +93,7 @@ describe('rule_add', () => { ) { const useKibanaMock = useKibana as jest.Mocked; const mocks = coreMock.createSetup(); - const { loadRuleTypes } = jest.requireMock('../../lib/rule_api'); + const { loadRuleTypes } = jest.requireMock('../../lib/rule_api/rule_types'); const ruleTypes = [ { id: 'my-rule-type', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index 76bef4c35788d..ec1f298b58ba0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -23,7 +23,8 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { ruleReducer, InitialRule, InitialRuleReducer } from './rule_reducer'; -import { createRule, loadRuleTypes } from '../../lib/rule_api'; +import { createRule } from '../../lib/rule_api/create'; +import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { ConfirmRuleSave } from './confirm_rule_save'; import { ConfirmRuleClose } from './confirm_rule_close'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx index 5830ab2bd9312..57ba467c79762 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx @@ -21,9 +21,13 @@ const actionTypeRegistry = actionTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); const useKibanaMock = useKibana as jest.Mocked; -jest.mock('../../lib/rule_api', () => ({ +jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../lib/rule_api/update', () => ({ updateRule: jest.fn().mockRejectedValue({ body: { message: 'Fail message' } }), +})); +jest.mock('../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -83,7 +87,7 @@ describe('rule_edit', () => { }, }; - const { loadRuleTypes } = jest.requireMock('../../lib/rule_api'); + const { loadRuleTypes } = jest.requireMock('../../lib/rule_api/rule_types'); const ruleTypes = [ { id: 'my-rule-type', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index e18d5997e5cf0..9354410ccc9a1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -38,7 +38,8 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { ruleReducer, ConcreteRuleReducer } from './rule_reducer'; -import { updateRule, loadRuleTypes } from '../../lib/rule_api'; +import { updateRule } from '../../lib/rule_api/update'; +import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { HealthContextProvider } from '../../context/health_context'; import { useKibana } from '../../../common/lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.tsx index f64412369af21..d4cd600f98ee5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.tsx @@ -8,11 +8,9 @@ import React, { useCallback, useState } from 'react'; import { useKibana } from '../../../../../common/lib/kibana'; import { SnoozeSchedule } from '../../../../../types'; -import { - loadRule, - snoozeRule as snoozeRuleApi, - unsnoozeRule as unsnoozeRuleApi, -} from '../../../../lib/rule_api'; +import { loadRule } from '../../../../lib/rule_api/get_rule'; +import { unsnoozeRule as unsnoozeRuleApi } from '../../../../lib/rule_api/unsnooze'; +import { snoozeRule as snoozeRuleApi } from '../../../../lib/rule_api/snooze'; import { RulesListNotifyBadge } from './notify_badge'; import { RulesListNotifyBadgePropsWithApi } from './types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx index 5d9b13eaded68..a40033e898582 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx @@ -9,7 +9,8 @@ import React, { useCallback, useMemo } from 'react'; import { EuiModal, EuiModalBody, EuiSpacer } from '@elastic/eui'; import { useKibana } from '../../../../common/lib/kibana'; -import { snoozeRule, unsnoozeRule } from '../../../lib/rule_api'; +import { snoozeRule } from '../../../lib/rule_api/snooze'; +import { unsnoozeRule } from '../../../lib/rule_api/unsnooze'; import { SNOOZE_FAILED_MESSAGE, SNOOZE_SUCCESS_MESSAGE, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 67c5f7f7ac200..f70437b33c451 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -50,21 +50,41 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadActionTypes: jest.fn(), loadAllActions: jest.fn(), })); -jest.mock('../../../lib/rule_api', () => ({ + +jest.mock('../../../lib/rule_api/rules_kuery_filter', () => ({ loadRulesWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate_kuery_filter', () => ({ loadRuleAggregationsWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ updateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), +})); +jest.mock('../../../lib/rule_api/snooze', () => ({ bulkSnoozeRules: jest.fn(), - bulkDeleteRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), +})); +jest.mock('../../../lib/rule_api/unsnooze', () => ({ bulkUnsnoozeRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_delete', () => ({ + bulkDeleteRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); + jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); @@ -96,7 +116,10 @@ jest.mock('../../../../common/get_experimental_features', () => ({ const ruleTags = ['a', 'b', 'c', 'd']; -const { loadRuleTypes, bulkUpdateAPIKey, loadRuleTags } = jest.requireMock('../../../lib/rule_api'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { bulkUpdateAPIKey } = jest.requireMock('../../../lib/rule_api/update_api_key'); +const { loadRuleTags } = jest.requireMock('../../../lib/rule_api/aggregate'); + const { loadRuleAggregationsWithKueryFilter } = jest.requireMock( '../../../lib/rule_api/aggregate_kuery_filter' ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index dd3e24bbf13d3..4fc42cea86872 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -56,14 +56,15 @@ import { BulkOperationPopover } from '../../common/components/bulk_operation_pop import { RuleQuickEditButtonsWithApi as RuleQuickEditButtons } from '../../common/components/rule_quick_edit_buttons'; import { CollapsedItemActionsWithApi as CollapsedItemActions } from './collapsed_item_actions'; import { RulesListFiltersBar } from './rules_list_filters_bar'; -import { - snoozeRule, - unsnoozeRule, - bulkUpdateAPIKey, - bulkDisableRules, - bulkEnableRules, - cloneRule, -} from '../../../lib/rule_api'; + +import { snoozeRule } from '../../../lib/rule_api/snooze'; +import { unsnoozeRule } from '../../../lib/rule_api/unsnooze'; +import { bulkUpdateAPIKey } from '../../../lib/rule_api/update_api_key'; +import { bulkDisableRules } from '../../../lib/rule_api/bulk_disable'; +import { bulkEnableRules } from '../../../lib/rule_api/bulk_enable'; +import { bulkDeleteRules } from '../../../lib/rule_api/bulk_delete'; +import { cloneRule } from '../../../lib/rule_api/clone'; + import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; import { RulesDeleteModalConfirmation } from '../../../components/rules_delete_modal_confirmation'; @@ -81,7 +82,6 @@ import { BulkSnoozeModalWithApi as BulkSnoozeModal } from './bulk_snooze_modal'; import { BulkSnoozeScheduleModalWithApi as BulkSnoozeScheduleModal } from './bulk_snooze_schedule_modal'; import { useBulkEditSelect } from '../../../hooks/use_bulk_edit_select'; import { runRule } from '../../../lib/run_rule'; -import { bulkDeleteRules } from '../../../lib/rule_api'; import { useLoadActionTypesQuery } from '../../../hooks/use_load_action_types_query'; import { useLoadRuleAggregationsQuery } from '../../../hooks/use_load_rule_aggregations_query'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx index 1b4b3cf7970ae..6e9276018f9b9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx @@ -31,13 +31,25 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), })); -jest.mock('../../../lib/rule_api', () => ({ +jest.mock('../../../lib/rule_api/rules_kuery_filter', () => ({ loadRulesWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate_kuery_filter', () => ({ loadRuleAggregationsWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ updateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_delete', () => ({ bulkDeleteRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), +})); +jest.mock('../../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -74,7 +86,8 @@ jest.mock('../../../../common/get_experimental_features', () => ({ getIsExperimentalFeatureEnabled: jest.fn(), })); -const { loadRuleTypes, bulkDeleteRules } = jest.requireMock('../../../lib/rule_api'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { bulkDeleteRules } = jest.requireMock('../../../lib/rule_api/bulk_delete'); const { loadRulesWithKueryFilter } = jest.requireMock('../../../lib/rule_api/rules_kuery_filter'); const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/action_connector_api'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx index 19fe6c68f2646..eb3be548b59fe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx @@ -31,13 +31,25 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), })); -jest.mock('../../../lib/rule_api', () => ({ +jest.mock('../../../lib/rule_api/rules_kuery_filter', () => ({ loadRulesWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate_kuery_filter', () => ({ loadRuleAggregationsWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ updateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_disable', () => ({ bulkDisableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), +})); +jest.mock('../../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -74,7 +86,8 @@ jest.mock('../../../../common/get_experimental_features', () => ({ getIsExperimentalFeatureEnabled: jest.fn(), })); -const { loadRuleTypes, bulkDisableRules } = jest.requireMock('../../../lib/rule_api'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { bulkDisableRules } = jest.requireMock('../../../lib/rule_api/bulk_disable'); const { loadRulesWithKueryFilter } = jest.requireMock('../../../lib/rule_api/rules_kuery_filter'); const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/action_connector_api'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx index 48a9995d7a82a..392ebdd8ca48c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx @@ -30,15 +30,31 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), })); -jest.mock('../../../lib/rule_api', () => ({ +jest.mock('../../../lib/rule_api/rules_kuery_filter', () => ({ loadRulesWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate_kuery_filter', () => ({ loadRuleAggregationsWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ updateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), +})); +jest.mock('../../../lib/rule_api/snooze', () => ({ bulkSnoozeRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/unsnooze', () => ({ bulkUnsnoozeRules: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -75,8 +91,10 @@ jest.mock('../../../../common/get_experimental_features', () => ({ getIsExperimentalFeatureEnabled: jest.fn(), })); -const { loadRuleTypes, bulkSnoozeRules, bulkUnsnoozeRules, bulkUpdateAPIKey } = - jest.requireMock('../../../lib/rule_api'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { bulkSnoozeRules } = jest.requireMock('../../../lib/rule_api/snooze'); +const { bulkUnsnoozeRules } = jest.requireMock('../../../lib/rule_api/unsnooze'); +const { bulkUpdateAPIKey } = jest.requireMock('../../../lib/rule_api/update_api_key'); const { loadRulesWithKueryFilter } = jest.requireMock('../../../lib/rule_api/rules_kuery_filter'); const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/action_connector_api'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx index 688e0ae50ec46..0cc662a28a69e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx @@ -31,13 +31,25 @@ jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), })); -jest.mock('../../../lib/rule_api', () => ({ +jest.mock('../../../lib/rule_api/rules_kuery_filter', () => ({ loadRulesWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate_kuery_filter', () => ({ loadRuleAggregationsWithKueryFilter: jest.fn(), +})); +jest.mock('../../../lib/rule_api/update_api_key', () => ({ updateAPIKey: jest.fn(), +})); +jest.mock('../../../lib/rule_api/aggregate', () => ({ loadRuleTags: jest.fn(), +})); +jest.mock('../../../lib/rule_api/bulk_enable', () => ({ bulkEnableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), +})); +jest.mock('../../../lib/rule_api/health', () => ({ alertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, @@ -74,7 +86,8 @@ jest.mock('../../../../common/get_experimental_features', () => ({ getIsExperimentalFeatureEnabled: jest.fn(), })); -const { loadRuleTypes, bulkEnableRules } = jest.requireMock('../../../lib/rule_api'); +const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); +const { bulkEnableRules } = jest.requireMock('../../../lib/rule_api/bulk_enable'); const { loadRulesWithKueryFilter } = jest.requireMock('../../../lib/rule_api/rules_kuery_filter'); const { loadActionTypes, loadAllActions } = jest.requireMock('../../../lib/action_connector_api'); diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 6763f9b1921d7..43bc6535df87a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -130,19 +130,10 @@ export type { export { Plugin } from './plugin'; // TODO remove this import when we expose the Rules tables as a component -export { loadRules } from './application/lib/rule_api/rules'; -export { loadExecutionLogAggregations } from './application/lib/rule_api/load_execution_log_aggregations'; -export { loadActionErrorLog } from './application/lib/rule_api/load_action_error_log'; -export { loadRuleTypes } from './application/lib/rule_api/rule_types'; export { loadRuleSummary } from './application/lib/rule_api/rule_summary'; -export { muteRule } from './application/lib/rule_api/mute'; export { bulkDeleteRules } from './application/lib/rule_api/bulk_delete'; -export { unmuteRule } from './application/lib/rule_api/unmute'; -export { snoozeRule } from './application/lib/rule_api/snooze'; -export { unsnoozeRule } from './application/lib/rule_api/unsnooze'; -export { loadRuleAggregations, loadRuleTags } from './application/lib/rule_api/aggregate'; +export { loadRuleAggregations } from './application/lib/rule_api/aggregate'; export { loadRule } from './application/lib/rule_api/get_rule'; -export { loadAllActions } from './application/lib/action_connector_api'; export { suspendedComponentWithProps } from './application/lib/suspended_component_with_props'; export { loadActionTypes } from './application/lib/action_connector_api/connector_types'; export { TIME_UNITS } from './application/constants'; From 2bfbddbd1dde19492965ec8f496fda3f4f66e1da Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:25:24 -0600 Subject: [PATCH 8/8] [Security Solution]Alert page chart selection default to summary (#151073) ## Summary This PR addresses a visual bug mentioned in https://github.com/elastic/kibana/issues/151044. Before implementing the `Summary` option, the charts defaults to `Trend`, this PR changes the default to `Summary` when `isAlertsPageCharts` is enabled. **Before** ![image](https://user-images.githubusercontent.com/18648970/218564106-fba96942-cb11-4f57-97ba-a06afccd2d15.png) **After** ![image](https://user-images.githubusercontent.com/18648970/218564231-3103f39f-7d33-43f9-b435-92470491b7ef.png) ### Checklist - [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 ### Note for desk testing Desk testing can be tricky because the selection is stored in local storage. The fastest way I found to test is to use a browser you don't use often (like Safari), clear all browsing data, cache etc., and then open kibana. --- .../alerts_local_storage/index.test.tsx | 27 ++++++++++++++++++- .../alerts_local_storage/index.tsx | 6 +++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.test.tsx index 1787955fb1b52..8145fcf698704 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.test.tsx @@ -10,13 +10,18 @@ import React from 'react'; import { useAlertsLocalStorage } from '.'; import { TestProviders } from '../../../../../common/mock'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; + +const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; +jest.mock('../../../../../common/hooks/use_experimental_features'); describe('useAlertsLocalStorage', () => { const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); - test('it returns the expected defaults', () => { + test('it returns the expected defaults when isAlertsPageCharts is disabled', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); const { result } = renderHook(() => useAlertsLocalStorage(), { wrapper }); const defaults = Object.fromEntries( @@ -34,4 +39,24 @@ describe('useAlertsLocalStorage', () => { trendChartStackBy: 'kibana.alert.rule.name', }); }); + + test('it returns the expected defaults when isAlertsPageCharts is enaabled', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + const { result } = renderHook(() => useAlertsLocalStorage(), { wrapper }); + + const defaults = Object.fromEntries( + Object.entries(result.current).filter((x) => typeof x[1] !== 'function') + ); + + expect(defaults).toEqual({ + alertViewSelection: 'charts', // default to the summary + countTableStackBy0: 'kibana.alert.rule.name', + countTableStackBy1: 'host.name', + groupBySelection: 'host.name', + isTreemapPanelExpanded: true, + riskChartStackBy0: 'kibana.alert.rule.name', + riskChartStackBy1: 'host.name', + trendChartStackBy: 'kibana.alert.rule.name', + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.tsx index 4af424c0ead8b..468edd14575c2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/alerts_local_storage/index.tsx @@ -30,12 +30,14 @@ import { } from '../../../../components/alerts_kpis/common/config'; import type { AlertsSettings } from './types'; import type { AlertViewSelection } from '../chart_select/helpers'; -import { TREND_ID } from '../chart_select/helpers'; +import { CHARTS_ID, TREND_ID } from '../chart_select/helpers'; import type { GroupBySelection } from '../../../../components/alerts_kpis/alerts_progress_bar_panel/types'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; export const useAlertsLocalStorage = (): AlertsSettings => { + const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); const [alertViewSelection, setAlertViewSelection] = useLocalStorage({ - defaultValue: TREND_ID, + defaultValue: isAlertsPageChartsEnabled ? CHARTS_ID : TREND_ID, key: getSettingKey({ category: VIEW_CATEGORY, page: ALERTS_PAGE,