Skip to content

Commit 60c74f0

Browse files
kibanamachinerbrtj
andauthored
[9.1] [ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured (elastic#233085) (elastic#233633)
# Backport This will backport the following commits from `main` to `9.1`: - [[ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured (elastic#233085)](elastic#233085) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Robert Jaszczurek","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-09-01T12:09:45Z","message":"[ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured (elastic#233085)\n\nUpdates the import path that caused the page to break when no filter\nlists have been created.\nThe issue:\n\n\nhttps://github.com/user-attachments/assets/78f8badf-0b7e-4fa6-844e-12254f3a6474","sha":"e5a8b2befdc65292b2646b2de97cceedfe72653c","branchLabelMapping":{"^v9.2.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","Team:ML","backport:version","v9.2.0","v9.1.3"],"title":"[ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured","number":233085,"url":"https://github.com/elastic/kibana/pull/233085","mergeCommit":{"message":"[ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured (elastic#233085)\n\nUpdates the import path that caused the page to break when no filter\nlists have been created.\nThe issue:\n\n\nhttps://github.com/user-attachments/assets/78f8badf-0b7e-4fa6-844e-12254f3a6474","sha":"e5a8b2befdc65292b2646b2de97cceedfe72653c"}},"sourceBranch":"main","suggestedTargetBranches":["9.1"],"targetPullRequestStates":[{"branch":"main","label":"v9.2.0","branchLabelMappingKey":"^v9.2.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/233085","number":233085,"mergeCommit":{"message":"[ML] Anomaly Explorer: Fixes rule editor flyout when no filter lists have been configured (elastic#233085)\n\nUpdates the import path that caused the page to break when no filter\nlists have been created.\nThe issue:\n\n\nhttps://github.com/user-attachments/assets/78f8badf-0b7e-4fa6-844e-12254f3a6474","sha":"e5a8b2befdc65292b2646b2de97cceedfe72653c"}},{"branch":"9.1","label":"v9.1.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Robert Jaszczurek <[email protected]>
1 parent fe5b7f2 commit 60c74f0

File tree

11 files changed

+230
-4
lines changed

11 files changed

+230
-4
lines changed

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/__snapshots__/scope_expression.test.js.snap

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/__snapshots__/scope_section.test.js.snap

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/rule_editor_flyout.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,12 @@ class RuleEditorFlyoutUI extends Component {
726726
</EuiButtonEmpty>
727727
</EuiFlexItem>
728728
<EuiFlexItem grow={false}>
729-
<EuiButton onClick={this.saveEdit} isDisabled={!isValidRule(rule)} fill>
729+
<EuiButton
730+
onClick={this.saveEdit}
731+
isDisabled={!isValidRule(rule)}
732+
fill
733+
data-test-subj="mlRuleEditorSaveButton"
734+
>
730735
<FormattedMessage
731736
id="xpack.ml.ruleEditor.ruleEditorFlyout.saveButtonLabel"
732737
defaultMessage="Save"

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/scope_expression.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export class ScopeExpression extends Component {
8787
<EuiSelect
8888
value={filterType}
8989
onChange={this.onChangeFilterType}
90+
data-test-subj="mlScopeFilterTypeSelect"
9091
options={[
9192
{
9293
value: ML_DETECTOR_RULE_FILTER_TYPE.INCLUDE,
@@ -104,6 +105,7 @@ export class ScopeExpression extends Component {
104105
<EuiSelect
105106
value={filterId}
106107
onChange={this.onChangeFilterId}
108+
data-test-subj="mlScopeFilterIdSelect"
107109
options={getFilterListOptions(filterListIds)}
108110
/>
109111
</EuiFlexItem>
@@ -158,6 +160,7 @@ export class ScopeExpression extends Component {
158160
value={filterId || ''}
159161
isActive={this.state.isFilterListOpen}
160162
onClick={this.openFilterList}
163+
data-test-subj="mlScopeExpressionFilterSelector"
161164
/>
162165
}
163166
isOpen={this.state.isFilterListOpen}

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/scope_section.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { getScopeFieldDefaults } from './utils';
2020
import { FormattedMessage } from '@kbn/i18n-react';
2121
import { ML_PAGES } from '../../../../common/constants/locator';
2222
import { MANAGEMENT_SECTION_IDS } from '../../management';
23-
import { useCreateAndNavigateToManagementMlLink } from '../../contexts/kibana';
23+
import { useCreateAndNavigateToManagementMlLink } from '../../contexts/kibana/use_create_url';
2424

2525
function NoFilterListsCallOut() {
2626
const redirectToFilterManagementPage = useCreateAndNavigateToManagementMlLink(
@@ -45,7 +45,10 @@ function NoFilterListsCallOut() {
4545
to create the list of values you want to include or exclude in the job rule."
4646
values={{
4747
filterListsLink: (
48-
<EuiLink onClick={redirectToFilterManagementPage}>
48+
<EuiLink
49+
onClick={redirectToFilterManagementPage}
50+
data-test-subj="mlScopeNoFilterListsLink"
51+
>
4952
<FormattedMessage
5053
id="xpack.ml.ruleEditor.scopeSection.createFilterListsDescription.filterListsLinkText"
5154
defaultMessage="Filter Lists"
@@ -128,6 +131,7 @@ export function ScopeSection({
128131
<EuiSpacer size="s" />
129132
<EuiCheckbox
130133
id="enable_scope_checkbox"
134+
data-test-subj="mlScopeEnableCheckbox"
131135
label={
132136
<FormattedMessage
133137
id="xpack.ml.ruleEditor.scopeSection.addFilterListLabel"

x-pack/platform/plugins/shared/ml/public/application/components/rule_editor/scope_section.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jest.mock('../../capabilities/check_capabilities', () => ({
1616
checkPermission: (privilege) => mockCheckPermission(privilege),
1717
}));
1818

19-
jest.mock('../../contexts/kibana', () => ({
19+
jest.mock('../../contexts/kibana/use_create_url', () => ({
2020
useCreateAndNavigateToManagementMlLink: jest.fn().mockReturnValue(jest.fn()),
2121
}));
2222

x-pack/platform/test/functional/apps/ml/anomaly_detection_result_views/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
3737
loadTestFile(require.resolve('./anomaly_explorer'));
3838
loadTestFile(require.resolve('./forecasts'));
3939
loadTestFile(require.resolve('./single_metric_viewer'));
40+
loadTestFile(require.resolve('./rule_editor_flyout'));
4041
});
4142
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { Job, Datafeed } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs';
9+
import type { FtrProviderContext } from '../../../ftr_provider_context';
10+
11+
// Test data: advanced job with partitioning fields so the Scope section is relevant.
12+
// @ts-expect-error not full interface
13+
const JOB_CONFIG: Job = {
14+
job_id: 'ecom_rule_editor_flyout',
15+
description:
16+
'mean(taxless_total_price) over "geoip.city_name" partitionfield=day_of_week on ecommerce dataset with 15m bucket span',
17+
groups: ['ecommerce', 'automated', 'advanced'],
18+
analysis_config: {
19+
bucket_span: '15m',
20+
detectors: [
21+
{
22+
detector_description:
23+
'mean(taxless_total_price) over "geoip.city_name" partitionfield=day_of_week',
24+
function: 'mean',
25+
field_name: 'taxless_total_price',
26+
over_field_name: 'geoip.city_name',
27+
partition_field_name: 'day_of_week',
28+
},
29+
],
30+
influencers: ['day_of_week'],
31+
},
32+
data_description: {
33+
time_field: 'order_date',
34+
time_format: 'epoch_ms',
35+
},
36+
analysis_limits: {
37+
model_memory_limit: '11mb',
38+
categorization_examples_limit: 4,
39+
},
40+
model_plot_config: { enabled: true },
41+
};
42+
43+
// @ts-expect-error not full interface
44+
const DATAFEED_CONFIG: Datafeed = {
45+
datafeed_id: 'datafeed-ecom_rule_editor_flyout',
46+
indices: ['ft_ecommerce'],
47+
job_id: 'ecom_rule_editor_flyout',
48+
query: { bool: { must: [{ match_all: {} }] } },
49+
};
50+
51+
export default function ({ getService }: FtrProviderContext) {
52+
const esArchiver = getService('esArchiver');
53+
const ml = getService('ml');
54+
55+
describe('rule editor flyout', function () {
56+
this.tags(['ml']);
57+
58+
before(async () => {
59+
await esArchiver.loadIfNeeded('x-pack/platform/test/fixtures/es_archives/ml/ecommerce');
60+
await ml.testResources.createDataViewIfNeeded('ft_ecommerce', 'order_date');
61+
await ml.testResources.setKibanaTimeZoneToUTC();
62+
await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG);
63+
await ml.securityUI.loginAsMlPowerUser();
64+
});
65+
66+
after(async () => {
67+
await ml.api.cleanMlIndices();
68+
await ml.testResources.deleteDataViewByTitle('ft_ecommerce');
69+
});
70+
71+
it('opens job in single metric viewer and selects entities', async () => {
72+
await ml.testExecution.logTestStep('navigate to job list');
73+
await ml.navigation.navigateToStackManagementMlSection('anomaly_detection', 'ml-jobs-list');
74+
75+
await ml.testExecution.logTestStep('open job in single metric viewer');
76+
await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id, 1);
77+
await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id);
78+
await ml.commonUI.waitForMlLoadingIndicatorToDisappear();
79+
80+
await ml.testExecution.logTestStep('select entity values to load results');
81+
await ml.singleMetricViewer.assertDetectorInputExist();
82+
await ml.singleMetricViewer.assertDetectorInputValue('0');
83+
await ml.singleMetricViewer.assertEntityInputExist('day_of_week');
84+
await ml.singleMetricViewer.selectEntityValue('day_of_week', 'Friday');
85+
await ml.singleMetricViewer.assertEntityInputExist('geoip.city_name');
86+
await ml.singleMetricViewer.selectEntityValue('geoip.city_name', 'Abu Dhabi');
87+
88+
await ml.testExecution.logTestStep('results are visible');
89+
await ml.singleMetricViewer.assertChartExist();
90+
await ml.anomaliesTable.assertTableExists();
91+
await ml.anomaliesTable.assertTableNotEmpty();
92+
});
93+
94+
it('opens rule editor flyout and navigates to Filter Lists via Scope callout', async () => {
95+
await ml.testExecution.logTestStep('open anomalies actions menu and configure rules');
96+
await ml.anomaliesTable.assertAnomalyActionsMenuButtonExists(0);
97+
await ml.anomaliesTable.ensureAnomalyActionsMenuOpen(0);
98+
await ml.anomaliesTable.assertAnomalyActionConfigureRulesButtonExists(0);
99+
await ml.anomaliesTable.clickConfigureRulesButton(0);
100+
101+
await ml.testExecution.logTestStep(
102+
'enable Scope section to show callout when no filter lists exist'
103+
);
104+
await ml.ruleEditorFlyout.enableScope();
105+
106+
await ml.testExecution.logTestStep('click Filter Lists link and verify navigation');
107+
await ml.ruleEditorFlyout.navigateToFilterListsFromCallout();
108+
});
109+
110+
it('shows scope controls when filter lists exist and allows save', async () => {
111+
const filterId = 'ecom_rule_editor_test_filter';
112+
await ml.testExecution.logTestStep('ensure at least one filter list exists');
113+
await ml.api.createFilter(filterId, {
114+
description: 'Functional test filter list for rule editor flyout',
115+
items: ['Friday', 'Abu Dhabi'],
116+
});
117+
118+
await ml.testExecution.logTestStep('re-open job in single metric viewer');
119+
await ml.navigation.navigateToStackManagementMlSection('anomaly_detection', 'ml-jobs-list');
120+
await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id, 1);
121+
await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id);
122+
await ml.commonUI.waitForMlLoadingIndicatorToDisappear();
123+
await ml.singleMetricViewer.assertDetectorInputExist();
124+
await ml.singleMetricViewer.assertDetectorInputValue('0');
125+
await ml.singleMetricViewer.selectEntityValue('day_of_week', 'Friday');
126+
await ml.singleMetricViewer.selectEntityValue('geoip.city_name', 'Abu Dhabi');
127+
128+
await ml.testExecution.logTestStep('open flyout via actions and enable Scope');
129+
await ml.anomaliesTable.ensureAnomalyActionsMenuOpen(0);
130+
await ml.anomaliesTable.assertAnomalyActionConfigureRulesButtonExists(0);
131+
await ml.anomaliesTable.clickConfigureRulesButton(0);
132+
await ml.ruleEditorFlyout.enableScope();
133+
134+
await ml.testExecution.logTestStep('open scope filter selector popover');
135+
await ml.ruleEditorFlyout.openScopeFilterSelector();
136+
137+
await ml.testExecution.logTestStep('save rule');
138+
await ml.ruleEditorFlyout.save();
139+
await ml.ruleEditorFlyout.closeIfOpen();
140+
141+
await ml.api.deleteFilter(filterId);
142+
});
143+
});
144+
}

x-pack/platform/test/functional/services/ml/anomalies_table.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export function MachineLearningAnomaliesTableProvider({ getService }: FtrProvide
124124
);
125125
},
126126

127+
async clickConfigureRulesButton(rowIndex: number) {
128+
await this.ensureAnomalyActionsMenuOpen(rowIndex);
129+
await testSubjects.click('mlAnomaliesListRowActionConfigureRulesButton');
130+
await testSubjects.existOrFail('mlRuleEditorFlyout');
131+
},
132+
127133
async assertAnomalyActionViewSeriesButtonEnabled(rowIndex: number, expectedValue: boolean) {
128134
await this.ensureAnomalyActionsMenuOpen(rowIndex);
129135
const isEnabled = await testSubjects.isEnabled('mlAnomaliesListRowActionViewSeriesButton');

x-pack/platform/test/functional/services/ml/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { MachineLearningSettingsProvider } from './settings';
4949
import { MachineLearningSettingsCalendarProvider } from './settings_calendar';
5050
import { MachineLearningSettingsFilterListProvider } from './settings_filter_list';
5151
import { MachineLearningSingleMetricViewerProvider } from './single_metric_viewer';
52+
import { MachineLearningRuleEditorFlyoutProvider } from './rule_editor_flyout';
5253
import { MachineLearningStackManagementJobsProvider } from './stack_management_jobs';
5354
import { MachineLearningTestExecutionProvider } from './test_execution';
5455
import { MachineLearningTestResourcesProvider } from './test_resources';
@@ -168,6 +169,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
168169
const settingsCalendar = MachineLearningSettingsCalendarProvider(context, commonUI);
169170
const settingsFilterList = MachineLearningSettingsFilterListProvider(context, commonUI);
170171
const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context, commonUI);
172+
const ruleEditorFlyout = MachineLearningRuleEditorFlyoutProvider(context);
171173
const tableService = MlTableServiceProvider(context);
172174
const stackManagementJobs = MachineLearningStackManagementJobsProvider(context, {
173175
jobTable,
@@ -238,6 +240,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
238240
settings,
239241
settingsCalendar,
240242
settingsFilterList,
243+
ruleEditorFlyout,
241244
singleMetricViewer,
242245
stackManagementJobs,
243246
suppliedConfigurations,

0 commit comments

Comments
 (0)