Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6a9382e
Support installing alert rules as kibana assets in packages
MichelLosier Jul 1, 2025
2e397e4
Introduce alert rule as epm kibana asset type
MichelLosier Jul 8, 2025
20d7524
WIP: Add alertingStart to app context, and alertingRulesClient to pac…
MichelLosier Jul 9, 2025
39f69c2
Establish steel thread for creating alert assets
MichelLosier Jul 10, 2025
a51e62f
Handle installing multiple alert assets
MichelLosier Jul 10, 2025
14b2df6
Include alert rules in pkg install results, support baseline tags
MichelLosier Jul 11, 2025
243e55c
Support asset tags for alert rules
MichelLosier Jul 11, 2025
baf108f
Support bulk uninstall of alert rules
MichelLosier Jul 14, 2025
969f3af
Support alert rules in streaming package install
MichelLosier Jul 14, 2025
518bf2a
Fix unit tests
MichelLosier Jul 14, 2025
47ded3c
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 15, 2025
c22d07b
WIP: Add alert rules client dependency to callers in package policy s…
MichelLosier Jul 15, 2025
06684cf
Revert providing alert rules client in sync integrations task
MichelLosier Jul 16, 2025
122a4d4
WIP: Have alertingRulesClient be optional for package install to supp…
MichelLosier Jul 16, 2025
43611a3
Revert "WIP: Have alertingRulesClient be optional for package install…
MichelLosier Jul 17, 2025
74b4c1d
Revert "Revert providing alert rules client in sync integrations task"
MichelLosier Jul 17, 2025
08f44e7
Revert "WIP: Add alert rules client dependency to callers in package …
MichelLosier Jul 17, 2025
b5615b2
Revert "Fix unit tests"
MichelLosier Jul 17, 2025
2fe4db1
Revert "Support alert rules in streaming package install"
MichelLosier Jul 17, 2025
1993549
Revert "Support bulk uninstall of alert rules"
MichelLosier Jul 17, 2025
53614cc
Revert "Support asset tags for alert rules"
MichelLosier Jul 17, 2025
93abd82
Revert "Include alert rules in pkg install results, support baseline …
MichelLosier Jul 17, 2025
9cd9f14
Revert "Handle installing multiple alert assets"
MichelLosier Jul 17, 2025
d6cb7fb
Revert "Establish steel thread for creating alert assets"
MichelLosier Jul 17, 2025
5bb3b9e
Revert "WIP: Add alertingStart to app context, and alertingRulesClien…
MichelLosier Jul 17, 2025
704804a
Revert "Introduce alert rule as epm kibana asset type"
MichelLosier Jul 17, 2025
6633b59
Support alert rule assets as saved object
MichelLosier Jul 17, 2025
c919665
Support tags for alert assets
MichelLosier Jul 17, 2025
8239310
Support alert asset tagging when streaming
MichelLosier Jul 17, 2025
2d21655
Include alert hidden type in SO client getters
MichelLosier Jul 17, 2025
0745b85
Fix type issues
MichelLosier Jul 17, 2025
59b507c
Support alert deletions on package uninstall
MichelLosier Jul 17, 2025
282e9f8
Add tests for fillAlertDefaults
MichelLosier Jul 17, 2025
586f7c1
Update FTR tests
MichelLosier Jul 18, 2025
0019f77
Cleanup file move from incorporating main
MichelLosier Jul 18, 2025
1d07345
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 18, 2025
cd0d8fd
Remove unused ts-ignore rule
MichelLosier Jul 18, 2025
a4b3416
Merge branch 'main' into support-alert-kbn-assets
MichelLosier Jul 22, 2025
a07e446
Put alert asset install behind feature flag enableAlertRuleTemplateSu…
MichelLosier Jul 28, 2025
5f1b88e
Merge branch 'main' into support-alert-kbn-assets
MichelLosier Jul 28, 2025
6b9f732
Add alert template feature flag to FTR tests
MichelLosier Jul 28, 2025
47d3587
Move inclusion of rule asset flag to base FTR config
MichelLosier Jul 29, 2025
41ac90a
Update flag name
MichelLosier Jul 30, 2025
c0f349e
Merge branch 'main' into support-alert-kbn-assets
MichelLosier Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const item: GetInfoResponse['item'] = {
],
assets: {
kibana: {
alert: [],
dashboard: [
{
pkgkey: 'nginx-0.7.0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const item: GetInfoResponse['item'] = {
],
assets: {
kibana: {
alert: [],
dashboard: [
{
pkgkey: 'okta-1.2.0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('Fleet - packageToPackagePolicy', () => {
ml_module: [],
security_ai_prompt: [],
security_rule: [],
alert: [],
tag: [],
osquery_pack_asset: [],
osquery_saved_query: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export enum KibanaAssetType {
securityAIPrompt = 'security_ai_prompt',
securityRule = 'security_rule',
cloudSecurityPostureRuleTemplate = 'csp_rule_template',
alert = 'alert',
osqueryPackAsset = 'osquery_pack_asset',
osquerySavedQuery = 'osquery_saved_query',
tag = 'tag',
Expand All @@ -81,6 +82,7 @@ export enum KibanaSavedObjectType {
securityAIPrompt = 'security-ai-prompt',
securityRule = 'security-rule',
cloudSecurityPostureRuleTemplate = 'csp-rule-template',
alert = 'alert',
osqueryPackAsset = 'osquery-pack-asset',
osquerySavedQuery = 'osquery-saved-query',
tag = 'tag',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export const AssetTitleMap: Record<
defaultMessage: 'Benchmark rules',
}
),
alert: i18n.translate('xpack.fleet.epm.assetTitles.alert', {
defaultMessage: 'Alert rules',
}),
'ml-module': i18n.translate('xpack.fleet.epm.assetTitles.mlModules', {
defaultMessage: 'Anomaly detection configurations',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ describe('schema validation', () => {
};
const assets: AssetsGroupedByServiceByType = {
kibana: {
alert: [],
dashboard: [],
visualization: [],
search: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type {
PostAgentPolicyCreateCallback,
PostAgentPolicyUpdateCallback,
} from '../types';
import { KibanaSavedObjectType } from '../types';
import type { FleetAppContext } from '../plugin';
import type { TelemetryEventsSender } from '../telemetry/sender';
import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE } from '../constants';
Expand Down Expand Up @@ -203,7 +204,7 @@ class AppContextService {

// soClient as kibana internal users, be careful on how you use it, security is not enabled
return appContextService.getSavedObjects().getScopedClient(request, {
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, KibanaSavedObjectType.alert],
excludedExtensions: [SECURITY_EXTENSION_ID],
});
}
Expand All @@ -223,7 +224,7 @@ class AppContextService {

// soClient as kibana internal users, be careful on how you use it, security is not enabled
return appContextService.getSavedObjects().getScopedClient(request, {
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, KibanaSavedObjectType.alert],
excludedExtensions: [SECURITY_EXTENSION_ID],
});
}
Expand All @@ -242,7 +243,7 @@ class AppContextService {
// soClient as kibana internal users, be careful on how you use it, security is not enabled
return appContextService.getSavedObjects().getScopedClient(fakeRequest, {
excludedExtensions: [SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID],
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, KibanaSavedObjectType.alert],
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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 { KibanaSavedObjectType } from '../../../../types';

import { fillAlertDefaults } from './alert';

describe('fillAlertDefaults', () => {
it('should fill in default values for alert saved object', () => {
const alertSo = {
id: 'test-alert',
type: KibanaSavedObjectType.alert,
attributes: {
name: 'Test Alert',
},
executionStatus: {
status: 'ok',
lastExecutionDate: '2023-10-01T00:00:00Z',
},
};

const context = {
pkgName: 'test-package',
spaceId: 'default',
assetTags: [],
};

const result = fillAlertDefaults(alertSo, context);

expect(result.id).toBe('test-alert');
expect((result.attributes as Record<string, unknown>).name).toBe('Test Alert');
expect(result.attributes.enabled).toBe(false);
expect(result.attributes.revision).toBe(0);
expect(result.attributes.executionStatus.status).toBe('pending');
expect(result.attributes.createdAt).toBeDefined();
expect(result.attributes.updatedAt).toBeDefined();
expect(result.attributes.running).toBe(false);
expect(result.attributes.tags).toEqual([
'fleet-pkg-test-package-default',
'fleet-managed-default',
]);
});
it('should include existing tags with default tags', () => {
const alertSo = {
id: 'test-alert',
type: KibanaSavedObjectType.alert,
attributes: {
name: 'Test Alert',
tags: ['existing-tag'],
},
};

const context = {
pkgName: 'test-package',
spaceId: 'default',
assetTags: [],
};

const result = fillAlertDefaults(alertSo, context);

expect(result.attributes.tags).toEqual([
'existing-tag',
'fleet-pkg-test-package-default',
'fleet-managed-default',
]);
});
it('should include tags from assetTags if they match', () => {
const alertSo = {
id: 'test-alert',
type: KibanaSavedObjectType.alert,
attributes: {
name: 'Test Alert',
},
};

const context = {
pkgName: 'test-package',
spaceId: 'default',
assetTags: [
{ text: 'alert-specific-tag', asset_types: ['alert'] },
{ text: 'other-asset-tag', asset_types: ['dashboard'] },
{ text: 'id-specific-tag', asset_ids: ['test-alert'] },
{ text: 'not-matching-id-tag', asset_ids: ['other-alert'] },
],
};

const result = fillAlertDefaults(alertSo, context);

expect(result.attributes.tags).toEqual([
'fleet-pkg-test-package-default',
'fleet-managed-default',
'alert-specific-tag',
'id-specific-tag',
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 { SavedObjectToBe, InstallAssetContext } from './install';
import { getPackageTagId, getManagedTagId } from './tag_assets';

export function fillAlertDefaults(alertSo: Partial<SavedObjectToBe>, context: InstallAssetContext) {
const currentDateTime = new Date().toISOString();
const tags = getTags(alertSo.id!, context);
const existingTags = Array.isArray((alertSo.attributes as Record<string, unknown>).tags)
? ((alertSo.attributes as Record<string, unknown>).tags as string[])
: [];

// Based on x-pack/platform/plugins/shared/alerting/server/application/rule/methods/create/create_rule.ts
const newSo = {
...alertSo,
attributes: {
...(alertSo.attributes ?? {}),
enabled: false,
revision: 0,
executionStatus: {
status: 'pending',
lastExecutionDate: currentDateTime,
},
createdAt: currentDateTime,
updatedAt: currentDateTime,
running: false,
tags: [...new Set([...existingTags, ...tags])],
},
};

return newSo;
}

function getTags(id: string, context: InstallAssetContext) {
const { pkgName, spaceId, assetTags } = context;
const tags = [getPackageTagId(spaceId, pkgName), getManagedTagId(spaceId)];

if (!assetTags || assetTags.length === 0) {
return tags;
}

const filteredAssetTags = assetTags.reduce<string[]>((_assetTags, assetTag) => {
if (assetTag.asset_types?.includes('alert') || assetTag.asset_ids?.includes(id)) {
return [..._assetTags, assetTag.text];
}
return _assetTags;
}, []);

return [...tags, ...filteredAssetTags];
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const createImportResponse = (
successCount: successResults.length,
} as SavedObjectsImportResponse);

const mockInstallAssetContext = {
pkgName: 'Mock package',
spaceId: 'default',
};

describe('installKibanaSavedObjects', () => {
beforeEach(() => {
mockImporter.import.mockReset();
Expand All @@ -65,6 +70,7 @@ describe('installKibanaSavedObjects', () => {
savedObjectsImporter: mockImporter,
logger: mockLogger,
kibanaAssets: [asset],
context: mockInstallAssetContext,
});

expect(mockImporter.import).toHaveBeenCalledTimes(2);
Expand All @@ -81,6 +87,7 @@ describe('installKibanaSavedObjects', () => {
savedObjectsImporter: mockImporter,
logger: mockLogger,
kibanaAssets: [asset],
context: mockInstallAssetContext,
})
).rejects.toEqual(expect.any(Error));
expect(mockImporter.import).toHaveBeenCalledTimes(51);
Expand All @@ -97,6 +104,7 @@ describe('installKibanaSavedObjects', () => {
savedObjectsImporter: mockImporter,
logger: mockLogger,
kibanaAssets: [asset],
context: mockInstallAssetContext,
})
).rejects.toEqual(expect.any(Error));
});
Expand All @@ -115,6 +123,7 @@ describe('installKibanaSavedObjects', () => {
savedObjectsImporter: mockImporter,
logger: mockLogger,
kibanaAssets: [asset],
context: mockInstallAssetContext,
});

expect(mockImporter.import).toHaveBeenCalledTimes(1);
Expand All @@ -128,7 +137,7 @@ describe('createSavedObjectKibanaAsset', () => {
attributes: { hello: 'world' },
migrationVersion: { dashboard: '8.6.0' },
});
const result = createSavedObjectKibanaAsset(asset);
const result = createSavedObjectKibanaAsset(asset, mockInstallAssetContext);

expect(result.typeMigrationVersion).toEqual('8.6.0');
});
Expand All @@ -139,7 +148,7 @@ describe('createSavedObjectKibanaAsset', () => {
typeMigrationVersion: '8.6.0',
coreMigrationVersion: '8.7.0',
});
const result = createSavedObjectKibanaAsset(asset);
const result = createSavedObjectKibanaAsset(asset, mockInstallAssetContext);

expect(result.typeMigrationVersion).toEqual('8.6.0');
expect(result.coreMigrationVersion).toEqual('8.7.0');
Expand Down
Loading
Loading