mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
# Backport This will backport the following commits from `main` to `8.17`: - [[ML] Transforms: Support wildcards in the alerting rule flyout (#204226)](https://github.com/elastic/kibana/pull/204226) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Dima Arnautov","email":"dmitrii.arnautov@elastic.co"},"sourceCommit":{"committedDate":"2024-12-17T13:28:39Z","message":"[ML] Transforms: Support wildcards in the alerting rule flyout (#204226)\n\n## Summary\r\n\r\n\r\nCloses #166810\r\n\r\n- Adds wildcards support for the tranform health alerting rule. \r\n- Populates transforms with alerting rules based on wildcard\r\nexpressions.\r\n- Excludes `alerting_rules` from the JSON tab. \r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"fd986432e896d4804ede18024ce1202c6ef77d6d","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement",":ml","Feature:Transforms","v9.0.0","Team:ML","backport:version","v8.18.0","v8.17.1"],"number":204226,"url":"https://github.com/elastic/kibana/pull/204226","mergeCommit":{"message":"[ML] Transforms: Support wildcards in the alerting rule flyout (#204226)\n\n## Summary\r\n\r\n\r\nCloses #166810\r\n\r\n- Adds wildcards support for the tranform health alerting rule. \r\n- Populates transforms with alerting rules based on wildcard\r\nexpressions.\r\n- Excludes `alerting_rules` from the JSON tab. \r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"fd986432e896d4804ede18024ce1202c6ef77d6d"}},"sourceBranch":"main","suggestedTargetBranches":["8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/204226","number":204226,"mergeCommit":{"message":"[ML] Transforms: Support wildcards in the alerting rule flyout (#204226)\n\n## Summary\r\n\r\n\r\nCloses #166810\r\n\r\n- Adds wildcards support for the tranform health alerting rule. \r\n- Populates transforms with alerting rules based on wildcard\r\nexpressions.\r\n- Excludes `alerting_rules` from the JSON tab. \r\n\r\n### Checklist\r\n\r\n- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"fd986432e896d4804ede18024ce1202c6ef77d6d"}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/204575","number":204575,"state":"MERGED","mergeCommit":{"sha":"dff3cc2943ec1ea9981dab84326a6e948456cd07","message":"[8.x] [ML] Transforms: Support wildcards in the alerting rule flyout (#204226) (#204575)\n\n# Backport\n\nThis will backport the following commits from `main` to `8.x`:\n- [[ML] Transforms: Support wildcards in the alerting rule flyout\n(#204226)](https://github.com/elastic/kibana/pull/204226)\n\n<!--- Backport version: 9.4.3 -->\n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT [{\"author\":{\"name\":\"Dima\nArnautov\",\"email\":\"dmitrii.arnautov@elastic.co\"},\"sourceCommit\":{\"committedDate\":\"2024-12-17T13:28:39Z\",\"message\":\"[ML]\nTransforms: Support wildcards in the alerting rule flyout\n(#204226)\\n\\n## Summary\\r\\n\\r\\n\\r\\nCloses #166810\\r\\n\\r\\n- Adds\nwildcards support for the tranform health alerting rule. \\r\\n- Populates\ntransforms with alerting rules based on wildcard\\r\\nexpressions.\\r\\n-\nExcludes `alerting_rules` from the JSON tab. \\r\\n\\r\\n###\nChecklist\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\\r\\n-\n[\n]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\",\"sha\":\"fd986432e896d4804ede18024ce1202c6ef77d6d\",\"branchLabelMapping\":{\"^v9.0.0$\":\"main\",\"^v8.18.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:enhancement\",\":ml\",\"Feature:Transforms\",\"v9.0.0\",\"Team:ML\",\"backport:version\",\"v8.18.0\",\"v8.17.1\"],\"title\":\"[ML]\nTransforms: Support wildcards in the alerting rule\nflyout\",\"number\":204226,\"url\":\"https://github.com/elastic/kibana/pull/204226\",\"mergeCommit\":{\"message\":\"[ML]\nTransforms: Support wildcards in the alerting rule flyout\n(#204226)\\n\\n## Summary\\r\\n\\r\\n\\r\\nCloses #166810\\r\\n\\r\\n- Adds\nwildcards support for the tranform health alerting rule. \\r\\n- Populates\ntransforms with alerting rules based on wildcard\\r\\nexpressions.\\r\\n-\nExcludes `alerting_rules` from the JSON tab. \\r\\n\\r\\n###\nChecklist\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\\r\\n-\n[\n]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\",\"sha\":\"fd986432e896d4804ede18024ce1202c6ef77d6d\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"8.x\",\"8.17\"],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v9.0.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/204226\",\"number\":204226,\"mergeCommit\":{\"message\":\"[ML]\nTransforms: Support wildcards in the alerting rule flyout\n(#204226)\\n\\n## Summary\\r\\n\\r\\n\\r\\nCloses #166810\\r\\n\\r\\n- Adds\nwildcards support for the tranform health alerting rule. \\r\\n- Populates\ntransforms with alerting rules based on wildcard\\r\\nexpressions.\\r\\n-\nExcludes `alerting_rules` from the JSON tab. \\r\\n\\r\\n###\nChecklist\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\\r\\n-\n[\n]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common\nscenarios\",\"sha\":\"fd986432e896d4804ede18024ce1202c6ef77d6d\"}},{\"branch\":\"8.x\",\"label\":\"v8.18.0\",\"branchLabelMappingKey\":\"^v8.18.0$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"8.17\",\"label\":\"v8.17.1\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"}]}]\nBACKPORT-->\n\nCo-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>"}},{"branch":"8.17","label":"v8.17.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
624406005f
commit
50701e0495
6 changed files with 343 additions and 67 deletions
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import type { TransformSelectorControlProps } from './transform_selector_control';
|
||||
import { TransformSelectorControl } from './transform_selector_control';
|
||||
|
||||
describe('TransformSelectorControl', () => {
|
||||
const defaultProps: TransformSelectorControlProps = {
|
||||
label: 'Select Transforms',
|
||||
errors: [],
|
||||
onChange: jest.fn(),
|
||||
selectedOptions: [],
|
||||
options: ['transform1', 'transform2'],
|
||||
allowSelectAll: true,
|
||||
};
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const { getByLabelText } = render(<TransformSelectorControl {...defaultProps} />);
|
||||
expect(getByLabelText('Select Transforms')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays options correctly', () => {
|
||||
const { getByText } = render(<TransformSelectorControl {...defaultProps} />);
|
||||
fireEvent.click(getByText('Select Transforms'));
|
||||
expect(getByText('transform1')).toBeInTheDocument();
|
||||
expect(getByText('transform2')).toBeInTheDocument();
|
||||
expect(getByText('*')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onChange with selected options', () => {
|
||||
const { getByText } = render(<TransformSelectorControl {...defaultProps} />);
|
||||
fireEvent.click(getByText('Select Transforms'));
|
||||
fireEvent.click(getByText('transform1'));
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith(['transform1']);
|
||||
});
|
||||
|
||||
it('only allows wildcards as custom options', () => {
|
||||
const { getByText, getByTestId } = render(<TransformSelectorControl {...defaultProps} />);
|
||||
fireEvent.click(getByText('Select Transforms'));
|
||||
const input = getByTestId('comboBoxSearchInput');
|
||||
|
||||
fireEvent.change(input, { target: { value: 'custom' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
expect(defaultProps.onChange).not.toHaveBeenCalledWith(['custom']);
|
||||
|
||||
fireEvent.change(input, { target: { value: 'custom*' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith(['custom*']);
|
||||
});
|
||||
|
||||
it('displays errors correctly', () => {
|
||||
const errorProps = { ...defaultProps, errors: ['Error message'] };
|
||||
const { getByText } = render(<TransformSelectorControl {...errorProps} />);
|
||||
expect(getByText('Error message')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -5,11 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiComboBoxProps } from '@elastic/eui';
|
||||
import type { EuiComboBoxOptionsListProps, EuiComboBoxProps } from '@elastic/eui';
|
||||
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
|
||||
import type { FC } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALL_TRANSFORMS_SELECTION } from '../../../common/constants';
|
||||
|
||||
export interface TransformSelectorControlProps {
|
||||
|
@ -33,6 +34,8 @@ export const TransformSelectorControl: FC<TransformSelectorControlProps> = ({
|
|||
options,
|
||||
allowSelectAll = false,
|
||||
}) => {
|
||||
const [allowCustomOptions, setAllowCustomOptions] = useState(false);
|
||||
|
||||
const onSelectionChange: EuiComboBoxProps<string>['onChange'] = ((selectionUpdate) => {
|
||||
if (!selectionUpdate?.length) {
|
||||
onChange([]);
|
||||
|
@ -50,6 +53,12 @@ export const TransformSelectorControl: FC<TransformSelectorControlProps> = ({
|
|||
);
|
||||
}) as Exclude<EuiComboBoxProps<string>['onChange'], undefined>;
|
||||
|
||||
const onCreateOption = allowCustomOptions
|
||||
? (((searchValue) => {
|
||||
onChange([...selectedOptions, searchValue]);
|
||||
}) as EuiComboBoxOptionsListProps<string>['onCreateOption'])
|
||||
: undefined;
|
||||
|
||||
const selectedOptionsEui = useMemo(() => convertToEuiOptions(selectedOptions), [selectedOptions]);
|
||||
const optionsEui = useMemo(() => {
|
||||
return convertToEuiOptions(allowSelectAll ? [ALL_TRANSFORMS_SELECTION, ...options] : options);
|
||||
|
@ -58,6 +67,17 @@ export const TransformSelectorControl: FC<TransformSelectorControlProps> = ({
|
|||
return (
|
||||
<EuiFormRow fullWidth label={label} isInvalid={!!errors?.length} error={errors}>
|
||||
<EuiComboBox<string>
|
||||
onSearchChange={(searchValue, hasMatchingOption) => {
|
||||
setAllowCustomOptions(!hasMatchingOption && searchValue.includes('*'));
|
||||
}}
|
||||
onCreateOption={onCreateOption}
|
||||
customOptionText={i18n.translate(
|
||||
'xpack.transform.alertTypes.transformHealth.customOptionText',
|
||||
{
|
||||
defaultMessage: 'Include {searchValuePlaceholder} wildcard',
|
||||
values: { searchValuePlaceholder: '{searchValue}' },
|
||||
}
|
||||
)}
|
||||
singleSelection={false}
|
||||
selectedOptions={selectedOptionsEui}
|
||||
options={optionsEui}
|
||||
|
|
|
@ -16,6 +16,12 @@ interface Props {
|
|||
}
|
||||
|
||||
export const ExpandedRowJsonPane: FC<Props> = ({ json }) => {
|
||||
// exclude alerting rules from the JSON
|
||||
if ('alerting_rules' in json) {
|
||||
const { alerting_rules: alertingRules, ...rest } = json;
|
||||
json = rest;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="transformJsonTabContent">
|
||||
<EuiFlexGroup>
|
||||
|
|
|
@ -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<ElasticsearchClient>;
|
||||
|
@ -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<TransformHealthRuleParams>);
|
||||
|
||||
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<TransformHealthRuleParams>);
|
||||
|
||||
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]],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Set<string>> => {
|
||||
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[];
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
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<TransformWithAlertingRules[]> {
|
||||
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<TransformHealthRuleParams>({
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue