diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 6f9a98033a943..0ee978c75d6c0 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -14,6 +14,7 @@ ], "requiredPlugins": [ "data", + "alerting", "ruleRegistry" ], "ui": true, diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 268e8e027736a..c4a44f47ecf09 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -5,11 +5,16 @@ * 2.0. */ -import { CoreSetup, PluginInitializerContext, KibanaRequest } from 'kibana/server'; +import { + CoreSetup, + PluginInitializerContext, + KibanaRequest, + RequestHandlerContext, +} from 'kibana/server'; +import { LicensingApiRequestHandlerContext } from '../../../../licensing/server'; import { PromiseReturnType } from '../../../typings/common'; import { createAnnotationsClient } from './create_annotations_client'; import { registerAnnotationAPIs } from './register_annotation_apis'; -import type { ObservabilityRequestHandlerContext } from '../../types'; interface Params { index: string; @@ -35,7 +40,7 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { return { getScopedAnnotationsClient: ( - requestContext: ObservabilityRequestHandlerContext, + requestContext: RequestHandlerContext & { licensing: LicensingApiRequestHandlerContext }, request: KibanaRequest ) => { return createAnnotationsClient({ diff --git a/x-pack/plugins/observability/server/routes/rules.ts b/x-pack/plugins/observability/server/routes/rules.ts index a24a41f9ba010..10f2f50886f07 100644 --- a/x-pack/plugins/observability/server/routes/rules.ts +++ b/x-pack/plugins/observability/server/routes/rules.ts @@ -29,8 +29,9 @@ const alertsListRoute = createObservabilityServerRoute({ ]), }), handler: async ({ ruleRegistry, context, params }) => { - const ruleRegistryClient = ruleRegistry.createScopedRuleRegistryClient({ + const ruleRegistryClient = await ruleRegistry.createScopedRuleRegistryClient({ context, + alertsClient: context.alerting.getAlertsClient(), }); if (!ruleRegistryClient) { @@ -57,8 +58,9 @@ const alertsDynamicIndexPatternRoute = createObservabilityServerRoute({ tags: [], }, handler: async ({ ruleRegistry, context }) => { - const ruleRegistryClient = ruleRegistry.createScopedRuleRegistryClient({ + const ruleRegistryClient = await ruleRegistry.createScopedRuleRegistryClient({ context, + alertsClient: context.alerting.getAlertsClient(), }); if (!ruleRegistryClient) { diff --git a/x-pack/plugins/observability/server/routes/types.ts b/x-pack/plugins/observability/server/routes/types.ts index 591ea2bda4135..0588bf8df2292 100644 --- a/x-pack/plugins/observability/server/routes/types.ts +++ b/x-pack/plugins/observability/server/routes/types.ts @@ -11,10 +11,11 @@ import type { ServerRoute, ServerRouteRepository, } from '@kbn/server-route-repository'; -import { CoreSetup, CoreStart, KibanaRequest, Logger, RequestHandlerContext } from 'kibana/server'; +import { CoreSetup, CoreStart, KibanaRequest, Logger } from 'kibana/server'; import { ObservabilityRuleRegistry } from '../plugin'; import { ObservabilityServerRouteRepository } from './get_global_observability_server_route_repository'; +import { ObservabilityRequestHandlerContext } from '../types'; export { ObservabilityServerRouteRepository }; @@ -25,7 +26,7 @@ export interface ObservabilityRouteHandlerResources { }; ruleRegistry: ObservabilityRuleRegistry; request: KibanaRequest; - context: RequestHandlerContext; + context: ObservabilityRequestHandlerContext; logger: Logger; } diff --git a/x-pack/plugins/observability/server/types.ts b/x-pack/plugins/observability/server/types.ts index bb2d69120d22c..81b32b3f8db7b 100644 --- a/x-pack/plugins/observability/server/types.ts +++ b/x-pack/plugins/observability/server/types.ts @@ -6,6 +6,7 @@ */ import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { AlertingApiRequestHandlerContext } from '../../alerting/server'; import type { ScopedRuleRegistryClient, FieldMapOf } from '../../rule_registry/server'; import type { LicensingApiRequestHandlerContext } from '../../licensing/server'; import type { ObservabilityRuleRegistry } from './plugin'; @@ -23,6 +24,7 @@ export type { */ export interface ObservabilityRequestHandlerContext extends RequestHandlerContext { licensing: LicensingApiRequestHandlerContext; + alerting: AlertingApiRequestHandlerContext; } /** diff --git a/x-pack/plugins/rule_registry/server/rule_registry/create_scoped_rule_registry_client/index.ts b/x-pack/plugins/rule_registry/server/rule_registry/create_scoped_rule_registry_client/index.ts index 29c4e42a28c7e..0d7735380b640 100644 --- a/x-pack/plugins/rule_registry/server/rule_registry/create_scoped_rule_registry_client/index.ts +++ b/x-pack/plugins/rule_registry/server/rule_registry/create_scoped_rule_registry_client/index.ts @@ -7,7 +7,7 @@ import { Either, isLeft, isRight } from 'fp-ts/lib/Either'; import { Errors } from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; -import { Logger, SavedObjectsClientContract } from 'kibana/server'; +import { Logger } from 'kibana/server'; import { IScopedClusterClient as ScopedClusterClient } from 'src/core/server'; import { castArray, compact } from 'lodash'; import { ESSearchRequest } from 'typings/elasticsearch'; @@ -18,33 +18,6 @@ import { ScopedRuleRegistryClient, EventsOf } from './types'; import { BaseRuleFieldMap } from '../../../common'; import { RuleRegistry } from '..'; -const getRuleUuids = async ({ - savedObjectsClient, - namespace, -}: { - savedObjectsClient: SavedObjectsClientContract; - namespace?: string; -}) => { - const options = { - type: 'alert', - ...(namespace ? { namespace } : {}), - }; - - const pitFinder = savedObjectsClient.createPointInTimeFinder({ - ...options, - }); - - const ruleUuids: string[] = []; - - for await (const response of pitFinder.find()) { - ruleUuids.push(...response.saved_objects.map((object) => object.id)); - } - - await pitFinder.close(); - - return ruleUuids; -}; - const createPathReporterError = (either: Either) => { const error = new Error(`Failed to validate alert event`); error.stack += '\n' + PathReporter.report(either).join('\n'); @@ -52,9 +25,8 @@ const createPathReporterError = (either: Either) => { }; export function createScopedRuleRegistryClient({ + ruleUuids, scopedClusterClient, - savedObjectsClient, - namespace, clusterClientAdapter, indexAliasName, indexTarget, @@ -62,9 +34,8 @@ export function createScopedRuleRegistryClient; index: string; @@ -99,11 +70,6 @@ export function createScopedRuleRegistryClient = { search: async (searchRequest) => { - const ruleUuids = await getRuleUuids({ - savedObjectsClient, - namespace, - }); - const fields = [ 'rule.id', ...(searchRequest.body?.fields ? castArray(searchRequest.body.fields) : []), diff --git a/x-pack/plugins/rule_registry/server/rule_registry/index.ts b/x-pack/plugins/rule_registry/server/rule_registry/index.ts index 07068fb3a8db2..bbc381f60a809 100644 --- a/x-pack/plugins/rule_registry/server/rule_registry/index.ts +++ b/x-pack/plugins/rule_registry/server/rule_registry/index.ts @@ -7,6 +7,7 @@ import { CoreSetup, Logger, RequestHandlerContext } from 'kibana/server'; import { inspect } from 'util'; +import { AlertsClient } from '../../../alerting/server'; import { SpacesServiceStart } from '../../../spaces/server'; import { ActionVariable, @@ -207,18 +208,28 @@ export class RuleRegistry { return this.children.find((child) => child.getRegistryByRuleTypeId(ruleTypeId)); } - createScopedRuleRegistryClient({ + async createScopedRuleRegistryClient({ context, + alertsClient, }: { context: RequestHandlerContext; - }): ScopedRuleRegistryClient | undefined { + alertsClient: AlertsClient; + }): Promise | undefined> { if (!this.options.writeEnabled) { return undefined; } const { indexAliasName, indexTarget } = this.getEsNames(); + const frameworkAlerts = ( + await alertsClient.find({ + options: { + perPage: 1000, + }, + }) + ).data; + return createScopedRuleRegistryClient({ - savedObjectsClient: context.core.savedObjects.getClient({ includedHiddenTypes: ['alert'] }), + ruleUuids: frameworkAlerts.map((frameworkAlert) => frameworkAlert.id), scopedClusterClient: context.core.elasticsearch.client, clusterClientAdapter: this.esAdapter, registry: this, @@ -246,7 +257,7 @@ export class RuleRegistry { >({ ...type, executor: async (executorOptions) => { - const { services, namespace, alertId, name, tags } = executorOptions; + const { services, alertId, name, tags } = executorOptions; const rule = { id: type.id, @@ -267,13 +278,12 @@ export class RuleRegistry { ...(this.options.writeEnabled ? { scopedRuleRegistryClient: createScopedRuleRegistryClient({ - savedObjectsClient: services.savedObjectsClient, scopedClusterClient: services.scopedClusterClient, + ruleUuids: [rule.uuid], clusterClientAdapter: this.esAdapter, registry: this, indexAliasName, indexTarget, - namespace, ruleData: { producer, rule, diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index 97026d126d2a1..da29fee6c178f 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { get, merge, omit } from 'lodash'; +import { format } from 'url'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; @@ -335,33 +336,54 @@ export default function ApiTest({ getService }: FtrProviderContext) { any >; - const toCompare = omit( - alertEvent, + expect(alertEvent['event.action']); + + const exclude = [ '@timestamp', 'kibana.rac.alert.start', 'kibana.rac.alert.uuid', - 'rule.uuid' - ); - - expectSnapshot(toCompare).toMatchInline(` - Object { - "event.action": "open", - "event.kind": "state", - "kibana.rac.alert.duration.us": 0, - "kibana.rac.alert.id": "apm.transaction_error_rate_opbeans-go_request", - "kibana.rac.alert.status": "open", - "kibana.rac.producer": "apm", - "rule.category": "Transaction error rate threshold", - "rule.id": "apm.transaction_error_rate", - "rule.name": "Transaction error rate threshold | opbeans-go", - "service.name": "opbeans-go", - "tags": Array [ - "apm", - "service.name:opbeans-go", - ], - "transaction.type": "request", - } - `); + 'rule.uuid', + ]; + + const toCompare = omit(alertEvent, exclude); + + expect(toCompare).to.eql({ + 'event.action': 'open', + 'event.kind': 'state', + 'kibana.rac.alert.duration.us': 0, + 'kibana.rac.alert.id': 'apm.transaction_error_rate_opbeans-go_request', + 'kibana.rac.alert.status': 'open', + 'kibana.rac.producer': 'apm', + 'kibana.observability.evaluation.threshold': 30, + 'kibana.observability.evaluation.value': 50, + 'processor.event': 'transaction', + 'rule.category': 'Transaction error rate threshold', + 'rule.id': 'apm.transaction_error_rate', + 'rule.name': 'Transaction error rate threshold | opbeans-go', + 'service.name': 'opbeans-go', + tags: ['apm', 'service.name:opbeans-go'], + 'transaction.type': 'request', + }); + + const now = new Date().getTime(); + + const { body: topAlerts, status: topAlertStatus } = await supertest + .get( + format({ + pathname: '/api/observability/rules/alerts/top', + query: { + start: new Date(now - 30 * 60 * 1000).toISOString(), + end: new Date(now).toISOString(), + }, + }) + ) + .set('kbn-xsrf', 'foo'); + + expect(topAlertStatus).to.eql(200); + + expect(topAlerts.length).to.be.greaterThan(0); + + expect(omit(topAlerts[0], exclude)).to.eql(toCompare); }); }); });