= ({ json }) => {
+ // exclude alerting rules from the JSON
+ if ('alerting_rules' in json) {
+ const { alerting_rules: alertingRules, ...rest } = json;
+ json = rest;
+ }
+
return (
diff --git a/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.test.ts b/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.test.ts
index ff502c9fcdc2..a799fbe0499f 100644
--- a/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.test.ts
+++ b/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.test.ts
@@ -5,16 +5,18 @@
* 2.0.
*/
-import { transformHealthServiceProvider } from './transform_health_service';
-import type { ElasticsearchClient } from '@kbn/core/server';
-import type { RulesClient } from '@kbn/alerting-plugin/server';
-import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
-import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
-import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock';
import type {
TransformGetTransformResponse,
TransformGetTransformStatsResponse,
+ TransformGetTransformTransformSummary,
} from '@elastic/elasticsearch/lib/api/types';
+import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server';
+import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock';
+import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
+import type { ElasticsearchClient } from '@kbn/core/server';
+import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
+import { transformHealthServiceProvider } from './transform_health_service';
+import type { TransformHealthRuleParams } from './schema';
describe('transformHealthServiceProvider', () => {
let esClient: jest.Mocked;
@@ -24,20 +26,48 @@ describe('transformHealthServiceProvider', () => {
beforeEach(() => {
esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
- (esClient.transform.getTransform as jest.Mock).mockResolvedValue({
- count: 3,
- transforms: [
- // Mock continuous transforms
- ...new Array(102).fill(null).map((_, i) => ({
- id: `transform${i}`,
- sync: true,
- })),
- {
- id: 'transform102',
- sync: false,
- },
- ],
- } as unknown as TransformGetTransformResponse);
+ (esClient.transform.getTransform as jest.Mock).mockImplementation(
+ async ({ transform_id: transformId }) => {
+ if (transformId === 'transform4,transform6,transform6*') {
+ // arrangement for exclude transforms
+ return {
+ transforms: [
+ {
+ id: `transform4`,
+ sync: true,
+ },
+ {
+ id: `transform6`,
+ sync: true,
+ },
+ ...new Array(10).fill(null).map((_, i) => ({
+ id: `transform6${i}`,
+ sync: true,
+ })),
+ ],
+ } as unknown as TransformGetTransformResponse;
+ } else {
+ return {
+ transforms: [
+ // Mock continuous transforms
+ ...new Array(102).fill(null).map((_, i) => ({
+ id: `transform${i}`,
+ sync: {
+ time: {
+ field: 'order_date',
+ delay: '60s',
+ },
+ },
+ })),
+ {
+ id: 'transform102',
+ },
+ ],
+ } as unknown as TransformGetTransformResponse;
+ }
+ }
+ );
+
(esClient.transform.getTransformStats as jest.Mock).mockResolvedValue({
count: 2,
transforms: [{}],
@@ -57,19 +87,27 @@ describe('transformHealthServiceProvider', () => {
const service = transformHealthServiceProvider({ esClient, rulesClient, fieldFormatsRegistry });
const result = await service.getHealthChecksResults({
includeTransforms: ['*'],
- excludeTransforms: ['transform4', 'transform6', 'transform62'],
+ excludeTransforms: ['transform4', 'transform6', 'transform6*'],
testsConfig: null,
});
+ expect(esClient.transform.getTransform).toHaveBeenCalledTimes(2);
+
+ expect(esClient.transform.getTransform).toHaveBeenCalledWith({
+ allow_no_match: true,
+ size: 1000,
+ });
expect(esClient.transform.getTransform).toHaveBeenCalledWith({
+ transform_id: 'transform4,transform6,transform6*',
allow_no_match: true,
size: 1000,
});
+
expect(esClient.transform.getTransformStats).toHaveBeenCalledTimes(1);
expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(1, {
basic: true,
transform_id:
- 'transform0,transform1,transform2,transform3,transform5,transform7,transform8,transform9,transform10,transform11,transform12,transform13,transform14,transform15,transform16,transform17,transform18,transform19,transform20,transform21,transform22,transform23,transform24,transform25,transform26,transform27,transform28,transform29,transform30,transform31,transform32,transform33,transform34,transform35,transform36,transform37,transform38,transform39,transform40,transform41,transform42,transform43,transform44,transform45,transform46,transform47,transform48,transform49,transform50,transform51,transform52,transform53,transform54,transform55,transform56,transform57,transform58,transform59,transform60,transform61,transform63,transform64,transform65,transform66,transform67,transform68,transform69,transform70,transform71,transform72,transform73,transform74,transform75,transform76,transform77,transform78,transform79,transform80,transform81,transform82,transform83,transform84,transform85,transform86,transform87,transform88,transform89,transform90,transform91,transform92,transform93,transform94,transform95,transform96,transform97,transform98,transform99,transform100,transform101',
+ 'transform0,transform1,transform2,transform3,transform5,transform7,transform8,transform9,transform10,transform11,transform12,transform13,transform14,transform15,transform16,transform17,transform18,transform19,transform20,transform21,transform22,transform23,transform24,transform25,transform26,transform27,transform28,transform29,transform30,transform31,transform32,transform33,transform34,transform35,transform36,transform37,transform38,transform39,transform40,transform41,transform42,transform43,transform44,transform45,transform46,transform47,transform48,transform49,transform50,transform51,transform52,transform53,transform54,transform55,transform56,transform57,transform58,transform59,transform70,transform71,transform72,transform73,transform74,transform75,transform76,transform77,transform78,transform79,transform80,transform81,transform82,transform83,transform84,transform85,transform86,transform87,transform88,transform89,transform90,transform91,transform92,transform93,transform94,transform95,transform96,transform97,transform98,transform99,transform100,transform101',
});
expect(result).toBeDefined();
@@ -126,4 +164,131 @@ describe('transformHealthServiceProvider', () => {
'Transform transform_with_a_very_long_id_that_result_in_long_url_for_sure_0, transform_with_a_very_long_id_that_result_in_long_url_for_sure_1, transform_with_a_very_long_id_that_result_in_long_url_for_sure_2, transform_with_a_very_long_id_that_result_in_long_url_for_sure_3, transform_with_a_very_long_id_that_result_in_long_url_for_sure_4, transform_with_a_very_long_id_that_result_in_long_url_for_sure_5, transform_with_a_very_long_id_that_result_in_long_url_for_sure_6, transform_with_a_very_long_id_that_result_in_long_url_for_sure_7, transform_with_a_very_long_id_that_result_in_long_url_for_sure_8, transform_with_a_very_long_id_that_result_in_long_url_for_sure_9, transform_with_a_very_long_id_that_result_in_long_url_for_sure_10, transform_with_a_very_long_id_that_result_in_long_url_for_sure_11, transform_with_a_very_long_id_that_result_in_long_url_for_sure_12, transform_with_a_very_long_id_that_result_in_long_url_for_sure_13, transform_with_a_very_long_id_that_result_in_long_url_for_sure_14, transform_with_a_very_long_id_that_result_in_long_url_for_sure_15, transform_with_a_very_long_id_that_result_in_long_url_for_sure_16, transform_with_a_very_long_id_that_result_in_long_url_for_sure_17, transform_with_a_very_long_id_that_result_in_long_url_for_sure_18, transform_with_a_very_long_id_that_result_in_long_url_for_sure_19, transform_with_a_very_long_id_that_result_in_long_url_for_sure_20, transform_with_a_very_long_id_that_result_in_long_url_for_sure_21, transform_with_a_very_long_id_that_result_in_long_url_for_sure_22, transform_with_a_very_long_id_that_result_in_long_url_for_sure_23, transform_with_a_very_long_id_that_result_in_long_url_for_sure_24, transform_with_a_very_long_id_that_result_in_long_url_for_sure_25, transform_with_a_very_long_id_that_result_in_long_url_for_sure_26, transform_with_a_very_long_id_that_result_in_long_url_for_sure_27, transform_with_a_very_long_id_that_result_in_long_url_for_sure_28, transform_with_a_very_long_id_that_result_in_long_url_for_sure_29, transform_with_a_very_long_id_that_result_in_long_url_for_sure_30, transform_with_a_very_long_id_that_result_in_long_url_for_sure_31, transform_with_a_very_long_id_that_result_in_long_url_for_sure_32, transform_with_a_very_long_id_that_result_in_long_url_for_sure_33, transform_with_a_very_long_id_that_result_in_long_url_for_sure_34, transform_with_a_very_long_id_that_result_in_long_url_for_sure_35, transform_with_a_very_long_id_that_result_in_long_url_for_sure_36, transform_with_a_very_long_id_that_result_in_long_url_for_sure_37, transform_with_a_very_long_id_that_result_in_long_url_for_sure_38, transform_with_a_very_long_id_that_result_in_long_url_for_sure_39, transform_with_a_very_long_id_that_result_in_long_url_for_sure_40, transform_with_a_very_long_id_that_result_in_long_url_for_sure_41, transform_with_a_very_long_id_that_result_in_long_url_for_sure_42, transform_with_a_very_long_id_that_result_in_long_url_for_sure_43, transform_with_a_very_long_id_that_result_in_long_url_for_sure_44, transform_with_a_very_long_id_that_result_in_long_url_for_sure_45, transform_with_a_very_long_id_that_result_in_long_url_for_sure_46, transform_with_a_very_long_id_that_result_in_long_url_for_sure_47, transform_with_a_very_long_id_that_result_in_long_url_for_sure_48, transform_with_a_very_long_id_that_result_in_long_url_for_sure_49, transform_with_a_very_long_id_that_result_in_long_url_for_sure_50, transform_with_a_very_long_id_that_result_in_long_url_for_sure_51, transform_with_a_very_long_id_that_result_in_long_url_for_sure_52, transform_with_a_very_long_id_that_result_in_long_url_for_sure_53, transform_with_a_very_long_id_that_result_in_long_url_for_sure_54, transform_with_a_very_long_id_that_result_in_long_url_for_sure_55, transform_with_a_very_long_id_that_result_in_long_url_for_sure_56, transform_with_a_very_long_id_that_result_in_long_url_for_sure_57, transform_with_a_very_long_id_that_result_in_long_url_for_sure_58, transform_with_a_very_long_id_that_result_in_long_url_for_sure_59 are not started.'
);
});
+
+ describe('populateTransformsWithAssignedRules', () => {
+ it('should throw an error if rulesClient is missing', async () => {
+ const service = transformHealthServiceProvider({ esClient, fieldFormatsRegistry });
+
+ await expect(service.populateTransformsWithAssignedRules([])).rejects.toThrow(
+ 'Rules client is missing'
+ );
+ });
+
+ it('should return an empty list if no transforms are provided', async () => {
+ const service = transformHealthServiceProvider({
+ esClient,
+ rulesClient,
+ fieldFormatsRegistry,
+ });
+
+ const result = await service.populateTransformsWithAssignedRules([]);
+ expect(result).toEqual([]);
+ });
+
+ it('should return transforms with associated alerting rules', async () => {
+ const transforms = [
+ { id: 'transform1', sync: {} },
+ { id: 'transform2', sync: {} },
+ { id: 'transform3', sync: {} },
+ ] as TransformGetTransformTransformSummary[];
+
+ const rules = [
+ {
+ id: 'rule1',
+ params: {
+ includeTransforms: ['transform1', 'transform2'],
+ excludeTransforms: [],
+ },
+ },
+ {
+ id: 'rule2',
+ params: {
+ includeTransforms: ['transform3'],
+ excludeTransforms: null,
+ },
+ },
+ ];
+
+ rulesClient.find.mockResolvedValue({ data: rules } as FindResult);
+
+ const service = transformHealthServiceProvider({
+ esClient,
+ rulesClient,
+ fieldFormatsRegistry,
+ });
+
+ const result = await service.populateTransformsWithAssignedRules(transforms);
+
+ expect(result).toEqual([
+ {
+ id: 'transform1',
+ sync: {},
+ alerting_rules: [rules[0]],
+ },
+ {
+ id: 'transform2',
+ sync: {},
+ alerting_rules: [rules[0]],
+ },
+ {
+ id: 'transform3',
+ sync: {},
+ alerting_rules: [rules[1]],
+ },
+ ]);
+ });
+
+ it('should exclude transforms based on excludeTransforms parameter', async () => {
+ const transforms = [
+ { id: 'transform1', sync: {} },
+ { id: 'transform2', sync: {} },
+ { id: 'transform3', sync: {} },
+ ] as TransformGetTransformTransformSummary[];
+
+ const rules = [
+ {
+ id: 'rule1',
+ params: {
+ includeTransforms: ['transform*'],
+ excludeTransforms: ['transform2'],
+ },
+ },
+ {
+ id: 'rule2',
+ params: {
+ includeTransforms: ['*'],
+ excludeTransforms: [],
+ },
+ },
+ ];
+
+ rulesClient.find.mockResolvedValue({ data: rules } as FindResult);
+
+ const service = transformHealthServiceProvider({
+ esClient,
+ rulesClient,
+ fieldFormatsRegistry,
+ });
+
+ const result = await service.populateTransformsWithAssignedRules(transforms);
+
+ expect(result).toEqual([
+ {
+ id: 'transform1',
+ sync: {},
+ alerting_rules: [rules[0], rules[1]],
+ },
+ {
+ id: 'transform2',
+ sync: {},
+ alerting_rules: [rules[1]],
+ },
+ {
+ id: 'transform3',
+ sync: {},
+ alerting_rules: [rules[0], rules[1]],
+ },
+ ]);
+ });
+ });
});
diff --git a/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.ts b/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.ts
index 939d4e01a6f1..288cb86842db 100644
--- a/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.ts
+++ b/x-pack/platform/plugins/private/transform/server/lib/alerting/transform_health_rule_type/transform_health_service.ts
@@ -38,11 +38,7 @@ interface TestResult {
context: TransformHealthAlertContext;
}
-type Transform = estypes.TransformGetTransformTransformSummary & {
- id: string;
- description?: string;
- sync: object;
-};
+type Transform = estypes.TransformGetTransformTransformSummary;
type TransformWithAlertingRules = Transform & { alerting_rules: TransformHealthAlertRule[] };
@@ -63,40 +59,44 @@ export function transformHealthServiceProvider({
* Resolves result transform selection. Only continuously running transforms are included.
* @param includeTransforms
* @param excludeTransforms
- * @param skipIDsCheck
*/
const getResultsTransformIds = async (
includeTransforms: string[],
- excludeTransforms: string[] | null,
- skipIDsCheck = false
+ excludeTransforms: string[] | null
): Promise> => {
const includeAll = includeTransforms.some((id) => id === ALL_TRANSFORMS_SELECTION);
let resultTransformIds: string[] = [];
- if (skipIDsCheck) {
- resultTransformIds = includeTransforms;
- } else {
- // Fetch transforms to make sure assigned transforms exists.
- const transformsResponse = (
- await esClient.transform.getTransform({
- ...(includeAll ? {} : { transform_id: includeTransforms.join(',') }),
- allow_no_match: true,
- size: 1000,
- })
- ).transforms as Transform[];
-
- transformsResponse.forEach((t) => {
- transformsDict.set(t.id, t);
- // Include only continuously running transforms.
- if (t.sync) {
- resultTransformIds.push(t.id);
- }
- });
- }
+ // Fetch transforms to make sure assigned transforms exists.
+ const transformsResponse = (
+ await esClient.transform.getTransform({
+ ...(includeAll ? {} : { transform_id: includeTransforms.join(',') }),
+ allow_no_match: true,
+ size: 1000,
+ })
+ ).transforms as Transform[];
+
+ transformsResponse.forEach((t) => {
+ transformsDict.set(t.id, t);
+ // Include only continuously running transforms.
+ if (isContinuousTransform(t)) {
+ resultTransformIds.push(t.id);
+ }
+ });
if (excludeTransforms && excludeTransforms.length > 0) {
- const excludeIdsSet = new Set(excludeTransforms);
+ let excludeIdsSet = new Set(excludeTransforms);
+ if (excludeTransforms.some((id) => id.includes('*'))) {
+ const excludeTransformResponse = (
+ await esClient.transform.getTransform({
+ transform_id: excludeTransforms.join(','),
+ allow_no_match: true,
+ size: 1000,
+ })
+ ).transforms as Transform[];
+ excludeIdsSet = new Set(excludeTransformResponse.map((t) => t.id));
+ }
resultTransformIds = resultTransformIds.filter((id) => !excludeIdsSet.has(id));
}
@@ -381,13 +381,19 @@ export function transformHealthServiceProvider({
async populateTransformsWithAssignedRules(
transforms: Transform[]
): Promise {
- const newList = transforms.filter(isContinuousTransform) as TransformWithAlertingRules[];
+ const continuousTransforms = transforms.filter(
+ isContinuousTransform
+ ) as TransformWithAlertingRules[];
if (!rulesClient) {
throw new Error('Rules client is missing');
}
- const transformMap = keyBy(newList, 'id');
+ if (!continuousTransforms.length) {
+ return transforms as TransformWithAlertingRules[];
+ }
+
+ const transformMap = keyBy(continuousTransforms, 'id');
const transformAlertingRules = await rulesClient.find({
options: {
@@ -398,12 +404,23 @@ export function transformHealthServiceProvider({
for (const ruleInstance of transformAlertingRules.data) {
// Retrieve result transform IDs
- const resultTransformIds = await getResultsTransformIds(
- ruleInstance.params.includeTransforms.includes(ALL_TRANSFORMS_SELECTION)
- ? Object.keys(transformMap)
- : ruleInstance.params.includeTransforms,
- ruleInstance.params.excludeTransforms,
- true
+ const { includeTransforms, excludeTransforms } = ruleInstance.params;
+
+ const resultTransformIds = new Set(
+ transforms
+ .filter(
+ (t) =>
+ includeTransforms.some((includedTransformId) =>
+ new RegExp(includedTransformId.replace(/\*/g, '.*')).test(t.id)
+ ) &&
+ (Array.isArray(excludeTransforms) && excludeTransforms.length > 0
+ ? excludeTransforms.every(
+ (excludedTransformId) =>
+ new RegExp(excludedTransformId.replace(/\*/g, '.*')).test(t.id) === false
+ )
+ : true)
+ )
+ .map((t) => t.id)
);
resultTransformIds.forEach((transformId) => {
@@ -419,7 +436,7 @@ export function transformHealthServiceProvider({
});
}
- return newList;
+ return continuousTransforms;
},
};
}
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts
index f760ac26f40c..ca0ba9a92f64 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts
@@ -82,7 +82,6 @@ export default function ruleTests({ getService }: FtrProviderContext) {
const objectRemover = new ObjectRemover(supertest);
let connectorId: string;
const transformId = 'test_transform_01';
- const destinationIndex = generateDestIndex(transformId);
beforeEach(async () => {
await esTestIndexTool.destroy();
@@ -98,8 +97,11 @@ export default function ruleTests({ getService }: FtrProviderContext) {
connectorId = await createConnector();
- await transform.api.createIndices(destinationIndex);
await createTransform(transformId);
+
+ // Create additional transforms to exclude from the rule
+ await createTransform('exclude_transform_01');
+ await createTransform('exclude_transform_02');
});
afterEach(async () => {
@@ -112,10 +114,12 @@ export default function ruleTests({ getService }: FtrProviderContext) {
it('runs correctly', async () => {
await stopTransform(transformId);
+ await stopTransform('exclude_transform_01');
const ruleId = await createRule({
name: 'Test all transforms',
includeTransforms: ['*'],
+ excludeTransforms: ['exclude_transform_*'],
});
log.debug('Checking created alerts...');
@@ -160,6 +164,8 @@ export default function ruleTests({ getService }: FtrProviderContext) {
}
async function createTransform(id: string) {
+ const destinationIndex = generateDestIndex(id);
+ await transform.api.createIndices(destinationIndex);
const config = generateTransformConfig(id);
await transform.api.createAndRunTransform(id, config);
}
@@ -183,20 +189,20 @@ export default function ruleTests({ getService }: FtrProviderContext) {
},
};
+ const { name, ...transformHealthRuleParams } = params;
+
const { status, body: createdRule } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send({
- name: params.name,
+ name,
consumer: 'alerts',
enabled: true,
rule_type_id: RULE_TYPE_ID,
schedule: { interval: '1d' },
actions: [action],
notify_when: 'onActiveAlert',
- params: {
- includeTransforms: params.includeTransforms,
- },
+ params: transformHealthRuleParams,
});
// will print the error body, if an error occurred