Skip to content

Commit 97233dd

Browse files
darnautovwalterra
andauthored
[ML] Clone analytics job (#59791) (#60228)
* [ML] clone analytics job * [ML] flyout clone header * [ML] improve clone action context menu item * [ML] support advanced job cloning * [ML] extractCloningConfig * [ML] fix isAdvancedSetting condition, add test * [ML] clone job header * [ML] job description placeholder * [ML] setEstimatedModelMemoryLimit on source index change * [ML] Fix types. * [ML] useUpdateEffect in create_analytics_form.tsx * [ML] setJobClone action * [ML] remove CreateAnalyticsFlyoutWrapper instance from the create_analytics_button.tsx * [ML] fix types * [ML] hack to align Clone button with the other actions * [ML] unknown props lead to advanced editor * [ML] rename maximum_number_trees ot max_trees * [ML] fix forceInput * [ML] populate excludesOptions on the first update, skip setting mml on the fist update * [ML] init functional test for cloning analytics jobs * [ML] functional tests * [ML] fix functional tests imports * [ML] fix indices names for functional tests * [ML] functional tests for outlier detection and regression jobs cloning * [ML] delete james tag * [ML] fix tests arrangement Co-authored-by: Walter Rafelsberger <walter@elastic.co> Co-authored-by: Walter Rafelsberger <walter@elastic.co>
1 parent d78150d commit 97233dd

File tree

26 files changed

+1132
-105
lines changed

26 files changed

+1132
-105
lines changed

x-pack/plugins/ml/__mocks__/shared_imports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
export function XJsonMode() {}
7+
export const XJsonMode = jest.fn();

x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,36 @@ export type IndexName = string;
1919
export type IndexPattern = string;
2020
export type DataFrameAnalyticsId = string;
2121

22+
export enum ANALYSIS_CONFIG_TYPE {
23+
OUTLIER_DETECTION = 'outlier_detection',
24+
REGRESSION = 'regression',
25+
CLASSIFICATION = 'classification',
26+
}
27+
2228
interface OutlierAnalysis {
29+
[key: string]: {};
2330
outlier_detection: {};
2431
}
2532

26-
interface RegressionAnalysis {
27-
regression: {
28-
dependent_variable: string;
29-
training_percent?: number;
30-
prediction_field_name?: string;
31-
};
33+
interface Regression {
34+
dependent_variable: string;
35+
training_percent?: number;
36+
prediction_field_name?: string;
37+
}
38+
export interface RegressionAnalysis {
39+
[key: string]: Regression;
40+
regression: Regression;
3241
}
3342

34-
interface ClassificationAnalysis {
35-
classification: {
36-
dependent_variable: string;
37-
training_percent?: number;
38-
num_top_classes?: string;
39-
prediction_field_name?: string;
40-
};
43+
interface Classification {
44+
dependent_variable: string;
45+
training_percent?: number;
46+
num_top_classes?: string;
47+
prediction_field_name?: string;
48+
}
49+
export interface ClassificationAnalysis {
50+
[key: string]: Classification;
51+
classification: Classification;
4152
}
4253

4354
export interface LoadExploreDataArg {
@@ -136,21 +147,14 @@ type AnalysisConfig =
136147
| ClassificationAnalysis
137148
| GenericAnalysis;
138149

139-
export enum ANALYSIS_CONFIG_TYPE {
140-
OUTLIER_DETECTION = 'outlier_detection',
141-
REGRESSION = 'regression',
142-
CLASSIFICATION = 'classification',
143-
UNKNOWN = 'unknown',
144-
}
145-
146150
export const getAnalysisType = (analysis: AnalysisConfig) => {
147151
const keys = Object.keys(analysis);
148152

149153
if (keys.length === 1) {
150154
return keys[0];
151155
}
152156

153-
return ANALYSIS_CONFIG_TYPE.UNKNOWN;
157+
return 'unknown';
154158
};
155159

156160
export const getDependentVar = (analysis: AnalysisConfig) => {
@@ -245,6 +249,7 @@ export interface DataFrameAnalyticsConfig {
245249
};
246250
source: {
247251
index: IndexName | IndexName[];
252+
query?: any;
248253
};
249254
analysis: AnalysisConfig;
250255
analyzed_fields: {
@@ -254,6 +259,7 @@ export interface DataFrameAnalyticsConfig {
254259
model_memory_limit: string;
255260
create_time: number;
256261
version: string;
262+
allow_lazy_start?: boolean;
257263
}
258264

259265
export enum REFRESH_ANALYTICS_LIST_STATE {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { isAdvancedConfig } from './action_clone';
8+
9+
describe('Analytics job clone action', () => {
10+
describe('isAdvancedConfig', () => {
11+
test('should detect a classification job created with the form', () => {
12+
const formCreatedClassificationJob = {
13+
description: "Classification job with 'bank-marketing' dataset",
14+
source: {
15+
index: ['bank-marketing'],
16+
query: {
17+
match_all: {},
18+
},
19+
},
20+
dest: {
21+
index: 'dest_bank_1',
22+
results_field: 'ml',
23+
},
24+
analysis: {
25+
classification: {
26+
dependent_variable: 'y',
27+
num_top_classes: 2,
28+
prediction_field_name: 'y_prediction',
29+
training_percent: 2,
30+
randomize_seed: 6233212276062807000,
31+
},
32+
},
33+
analyzed_fields: {
34+
includes: [],
35+
excludes: [],
36+
},
37+
model_memory_limit: '350mb',
38+
allow_lazy_start: false,
39+
};
40+
41+
expect(isAdvancedConfig(formCreatedClassificationJob)).toBe(false);
42+
});
43+
44+
test('should detect a outlier_detection job created with the form', () => {
45+
const formCreatedOutlierDetectionJob = {
46+
description: "Outlier detection job with 'glass' dataset",
47+
source: {
48+
index: ['glass_withoutdupl_norm'],
49+
query: {
50+
match_all: {},
51+
},
52+
},
53+
dest: {
54+
index: 'dest_glass_1',
55+
results_field: 'ml',
56+
},
57+
analysis: {
58+
outlier_detection: {
59+
compute_feature_influence: true,
60+
outlier_fraction: 0.05,
61+
standardization_enabled: true,
62+
},
63+
},
64+
analyzed_fields: {
65+
includes: [],
66+
excludes: ['id', 'outlier'],
67+
},
68+
model_memory_limit: '1mb',
69+
allow_lazy_start: false,
70+
};
71+
expect(isAdvancedConfig(formCreatedOutlierDetectionJob)).toBe(false);
72+
});
73+
74+
test('should detect a regression job created with the form', () => {
75+
const formCreatedRegressionJob = {
76+
description: "Regression job with 'electrical-grid-stability' dataset",
77+
source: {
78+
index: ['electrical-grid-stability'],
79+
query: {
80+
match_all: {},
81+
},
82+
},
83+
dest: {
84+
index: 'dest_grid_1',
85+
results_field: 'ml',
86+
},
87+
analysis: {
88+
regression: {
89+
dependent_variable: 'stab',
90+
prediction_field_name: 'stab_prediction',
91+
training_percent: 20,
92+
randomize_seed: -2228827740028660200,
93+
},
94+
},
95+
analyzed_fields: {
96+
includes: [],
97+
excludes: [],
98+
},
99+
model_memory_limit: '150mb',
100+
allow_lazy_start: false,
101+
};
102+
103+
expect(isAdvancedConfig(formCreatedRegressionJob)).toBe(false);
104+
});
105+
106+
test('should detect advanced classification job', () => {
107+
const advancedClassificationJob = {
108+
description: "Classification job with 'bank-marketing' dataset",
109+
source: {
110+
index: ['bank-marketing'],
111+
query: {
112+
match_all: {},
113+
},
114+
},
115+
dest: {
116+
index: 'dest_bank_1',
117+
results_field: 'CUSTOM_RESULT_FIELD',
118+
},
119+
analysis: {
120+
classification: {
121+
dependent_variable: 'y',
122+
num_top_classes: 2,
123+
prediction_field_name: 'y_prediction',
124+
training_percent: 2,
125+
randomize_seed: 6233212276062807000,
126+
},
127+
},
128+
analyzed_fields: {
129+
includes: [],
130+
excludes: [],
131+
},
132+
model_memory_limit: '350mb',
133+
allow_lazy_start: false,
134+
};
135+
136+
expect(isAdvancedConfig(advancedClassificationJob)).toBe(true);
137+
});
138+
139+
test('should detect advanced outlier_detection job', () => {
140+
const advancedOutlierDetectionJob = {
141+
description: "Outlier detection job with 'glass' dataset",
142+
source: {
143+
index: ['glass_withoutdupl_norm'],
144+
query: {
145+
// TODO check default for `match`
146+
match_all: {},
147+
},
148+
},
149+
dest: {
150+
index: 'dest_glass_1',
151+
results_field: 'ml',
152+
},
153+
analysis: {
154+
outlier_detection: {
155+
compute_feature_influence: false,
156+
outlier_fraction: 0.05,
157+
standardization_enabled: true,
158+
},
159+
},
160+
analyzed_fields: {
161+
includes: [],
162+
excludes: ['id', 'outlier'],
163+
},
164+
model_memory_limit: '1mb',
165+
allow_lazy_start: false,
166+
};
167+
expect(isAdvancedConfig(advancedOutlierDetectionJob)).toBe(true);
168+
});
169+
170+
test('should detect a custom query', () => {
171+
const advancedRegressionJob = {
172+
description: "Regression job with 'electrical-grid-stability' dataset",
173+
source: {
174+
index: ['electrical-grid-stability'],
175+
query: {
176+
match: {
177+
custom_field: 'custom_match',
178+
},
179+
},
180+
},
181+
dest: {
182+
index: 'dest_grid_1',
183+
results_field: 'ml',
184+
},
185+
analysis: {
186+
regression: {
187+
dependent_variable: 'stab',
188+
prediction_field_name: 'stab_prediction',
189+
training_percent: 20,
190+
randomize_seed: -2228827740028660200,
191+
},
192+
},
193+
analyzed_fields: {
194+
includes: [],
195+
excludes: [],
196+
},
197+
model_memory_limit: '150mb',
198+
allow_lazy_start: false,
199+
};
200+
201+
expect(isAdvancedConfig(advancedRegressionJob)).toBe(true);
202+
});
203+
204+
test('should detect custom analysis settings', () => {
205+
const config = {
206+
description: "Classification clone with 'bank-marketing' dataset",
207+
source: {
208+
index: 'bank-marketing',
209+
},
210+
dest: {
211+
index: 'bank_classification4',
212+
},
213+
analyzed_fields: {
214+
excludes: [],
215+
},
216+
analysis: {
217+
classification: {
218+
dependent_variable: 'y',
219+
training_percent: 71,
220+
max_trees: 1500,
221+
},
222+
},
223+
model_memory_limit: '400mb',
224+
};
225+
226+
expect(isAdvancedConfig(config)).toBe(true);
227+
});
228+
229+
test('should detect as advanced if the prop is unknown', () => {
230+
const config = {
231+
description: "Classification clone with 'bank-marketing' dataset",
232+
source: {
233+
index: 'bank-marketing',
234+
},
235+
dest: {
236+
index: 'bank_classification4',
237+
},
238+
analyzed_fields: {
239+
excludes: [],
240+
},
241+
analysis: {
242+
classification: {
243+
dependent_variable: 'y',
244+
training_percent: 71,
245+
maximum_number_trees: 1500,
246+
},
247+
},
248+
model_memory_limit: '400mb',
249+
};
250+
251+
expect(isAdvancedConfig(config)).toBe(true);
252+
});
253+
});
254+
});

0 commit comments

Comments
 (0)