diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 9fdf113cc64af..4e44593dc7767 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -14,7 +14,7 @@ import { Logger, } from '@kbn/core/server'; import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; -import { PluginSetupContract } from '@kbn/alerting-plugin/server'; +import { PluginSetupContract, PluginStartContract } from '@kbn/alerting-plugin/server'; import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server'; import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; @@ -59,6 +59,10 @@ interface PluginSetup { usageCollection?: UsageCollectionSetup; } +interface PluginStart { + alerting: PluginStartContract; +} + export class ObservabilityPlugin implements Plugin { private logger: Logger; @@ -67,7 +71,7 @@ export class ObservabilityPlugin implements Plugin { this.logger = initContext.logger.get(); } - public setup(core: CoreSetup, plugins: PluginSetup) { + public setup(core: CoreSetup, plugins: PluginSetup) { const casesCapabilities = createCasesUICapabilities(); const casesApiTags = getCasesApiTags(observabilityFeatureId); @@ -237,13 +241,16 @@ export class ObservabilityPlugin implements Plugin { registerRuleTypes(plugins.alerting, this.logger, ruleDataClient, core.http.basePath); registerSloUsageCollector(plugins.usageCollection); - registerRoutes({ - core, - dependencies: { - ruleDataService, - }, - logger: this.logger, - repository: getObservabilityServerRouteRepository(), + core.getStartServices().then(([coreStart, pluginStart]) => { + registerRoutes({ + core, + dependencies: { + ruleDataService, + getRulesClientWithRequest: pluginStart.alerting.getRulesClientWithRequest, + }, + logger: this.logger, + repository: getObservabilityServerRouteRepository(), + }); }); /** diff --git a/x-pack/plugins/observability/server/routes/register_routes.ts b/x-pack/plugins/observability/server/routes/register_routes.ts index de46f624addb7..f79027bd4d6bb 100644 --- a/x-pack/plugins/observability/server/routes/register_routes.ts +++ b/x-pack/plugins/observability/server/routes/register_routes.ts @@ -10,10 +10,11 @@ import { parseEndpoint, routeValidationObject, } from '@kbn/server-route-repository'; -import { CoreSetup, Logger, RouteRegistrar } from '@kbn/core/server'; +import { CoreSetup, KibanaRequest, Logger, RouteRegistrar } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import { RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; +import { RulesClientApi } from '@kbn/alerting-plugin/server/types'; import { ObservabilityRequestHandlerContext } from '../types'; import { AbstractObservabilityServerRouteRepository } from './types'; @@ -28,6 +29,7 @@ interface RegisterRoutes { export interface RegisterRoutesDependencies { ruleDataService: RuleDataPluginService; + getRulesClientWithRequest: (request: KibanaRequest) => RulesClientApi; } export function registerRoutes({ repository, core, logger, dependencies }: RegisterRoutes) { diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 1aec94e3dde1f..0d0a964408773 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -106,18 +106,25 @@ const deleteSLORoute = createObservabilityServerRoute({ tags: ['access:slo_write'], }, params: deleteSLOParamsSchema, - handler: async ({ context, params, logger }) => { + handler: async ({ + request, + context, + params, + logger, + dependencies: { getRulesClientWithRequest }, + }) => { if (!isLicenseAtLeastPlatinum(context)) { throw badRequest('Platinum license or higher is needed to make use of this feature.'); } const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; + const rulesClient = getRulesClientWithRequest(request); const repository = new KibanaSavedObjectsSLORepository(soClient); const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); - const deleteSLO = new DeleteSLO(repository, transformManager, esClient); + const deleteSLO = new DeleteSLO(repository, transformManager, esClient, rulesClient); await deleteSLO.execute(params.path.id); }, diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts index a979265ce1ef9..e1e76fa56400d 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { RulesClientApi } from '@kbn/alerting-plugin/server/types'; +import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; import { ElasticsearchClient } from '@kbn/core/server'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { getSLOTransformId, SLO_INDEX_TEMPLATE_NAME } from '../../assets/constants'; @@ -18,17 +20,19 @@ describe('DeleteSLO', () => { let mockRepository: jest.Mocked; let mockTransformManager: jest.Mocked; let mockEsClient: jest.Mocked; + let mockRulesClient: jest.Mocked; let deleteSLO: DeleteSLO; beforeEach(() => { mockRepository = createSLORepositoryMock(); mockTransformManager = createTransformManagerMock(); mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); - deleteSLO = new DeleteSLO(mockRepository, mockTransformManager, mockEsClient); + mockRulesClient = rulesClientMock.create(); + deleteSLO = new DeleteSLO(mockRepository, mockTransformManager, mockEsClient, mockRulesClient); }); describe('happy path', () => { - it('removes the transform, the roll up data and the SLO from the repository', async () => { + it('removes the transform, the roll up data, the associated rules and the SLO from the repository', async () => { const slo = createSLO({ indicator: createAPMTransactionErrorRateIndicator() }); mockRepository.findById.mockResolvedValueOnce(slo); @@ -51,6 +55,9 @@ describe('DeleteSLO', () => { }, }) ); + expect(mockRulesClient.bulkDeleteRules).toHaveBeenCalledWith({ + filter: `alert.attributes.params.sloId:${slo.id}`, + }); expect(mockRepository.deleteById).toHaveBeenCalledWith(slo.id); }); }); diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.ts index 8ec8d2060f730..dc812b1bd4f31 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { RulesClientApi } from '@kbn/alerting-plugin/server/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { getSLOTransformId, SLO_INDEX_TEMPLATE_NAME } from '../../assets/constants'; @@ -15,7 +16,8 @@ export class DeleteSLO { constructor( private repository: SLORepository, private transformManager: TransformManager, - private esClient: ElasticsearchClient + private esClient: ElasticsearchClient, + private rulesClient: RulesClientApi ) {} public async execute(sloId: string): Promise { @@ -26,6 +28,7 @@ export class DeleteSLO { await this.transformManager.uninstall(sloTransformId); await this.deleteRollupData(slo.id); + await this.deleteAssociatedRules(slo.id); await this.repository.deleteById(slo.id); } @@ -40,4 +43,14 @@ export class DeleteSLO { }, }); } + + private async deleteAssociatedRules(sloId: string): Promise { + try { + await this.rulesClient.bulkDeleteRules({ + filter: `alert.attributes.params.sloId:${sloId}`, + }); + } catch (err) { + // no-op: bulkDeleteRules throws if no rules are found. + } + } }