Skip to content

Commit cc25792

Browse files
authored
[ML] fixing datafeed preview after allowing datafeed_config in job_config (#75625)
Since PR: #74265 It is valid to supply a `datafeed_config` inside of a job config. But, the `_preview` API allows BOTH a `datafeed_config` and `job_config` to be top-level objects. This can cause confusion and allowing both a top level `datafeed_config` and an internal `job_config.datafeed_config` is unsupported behavior. This commit protects against these weird scenarios and also strictly allows only a `job_config` with a nested `datafeed_config` to be provided. Consequently, on preview, the valid combinations are: - Datafeed config ID only - top level datafeed_config and top level job_config (without a nested datafeed_config) - top level job_config with a nested datafeed_config everything else is considered invalid
1 parent 98504ea commit cc25792

File tree

6 files changed

+172
-9
lines changed

6 files changed

+172
-9
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/PreviewDatafeedAction.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,19 @@ public Request build() {
178178
}
179179
if (jobBuilder != null) {
180180
jobBuilder.setId("preview_job_id");
181-
if (datafeedBuilder == null) {
182-
throw new IllegalArgumentException("[datafeed_config] must be present when a [job_config] is provided");
181+
if (datafeedBuilder == null && jobBuilder.getDatafeedConfig() == null) {
182+
throw new IllegalArgumentException(
183+
"[datafeed_config] must be present when a [job_config.datafeed_config] is not present"
184+
);
185+
}
186+
if (datafeedBuilder != null && jobBuilder.getDatafeedConfig() != null) {
187+
throw new IllegalArgumentException(
188+
"[datafeed_config] must not be present when a [job_config.datafeed_config] is present"
189+
);
190+
}
191+
// If the datafeed_config has been provided via the jobBuilder, set it here for easier serialization and use
192+
if (jobBuilder.getDatafeedConfig() != null) {
193+
datafeedBuilder = jobBuilder.getDatafeedConfig().setJobId(jobBuilder.getId()).setId(jobBuilder.getId());
183194
}
184195
}
185196
if (datafeedId != null && (datafeedBuilder != null || jobBuilder != null)) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Job.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,10 @@ public Builder setDatafeed(DatafeedConfig.Builder datafeed) {
928928
return this;
929929
}
930930

931+
public DatafeedConfig.Builder getDatafeedConfig() {
932+
return datafeedConfig;
933+
}
934+
931935
/**
932936
* This is used for parsing. If the datafeed_config exists AND its indices options are `null`, we set them to these options
933937
*

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/PreviewDatafeedActionRequestTests.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.test.AbstractWireSerializingTestCase;
1515
import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction.Request;
1616
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
17+
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfigBuilderTests;
1718
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfigTests;
1819
import org.elasticsearch.xpack.core.ml.job.config.JobTests;
1920

@@ -27,12 +28,25 @@ public class PreviewDatafeedActionRequestTests extends AbstractWireSerializingTe
2728
@Override
2829
protected Request createTestInstance() {
2930
String jobId = randomAlphaOfLength(10);
30-
return randomBoolean() ?
31-
new Request(randomAlphaOfLength(10)) :
32-
new Request(
33-
DatafeedConfigTests.createRandomizedDatafeedConfig(jobId),
34-
randomBoolean() ? JobTests.buildJobBuilder(jobId) : null
35-
);
31+
switch (randomInt(2)) {
32+
case 0:
33+
return new Request(randomAlphaOfLength(10));
34+
case 1:
35+
return new Request(
36+
DatafeedConfigTests.createRandomizedDatafeedConfig(jobId),
37+
randomBoolean() ? JobTests.buildJobBuilder(jobId) : null
38+
);
39+
case 2:
40+
return new Request.Builder()
41+
.setJobBuilder(
42+
JobTests.buildJobBuilder(jobId).setDatafeed(DatafeedConfigBuilderTests.createRandomizedDatafeedConfigBuilder(
43+
null,
44+
null,
45+
3600000
46+
))).build();
47+
default:
48+
throw new IllegalArgumentException("Unexpected test state");
49+
}
3650
}
3751

3852
@Override
@@ -62,6 +76,25 @@ public void testValidation() {
6276
ex = expectThrows(IllegalArgumentException.class, requestBuilder::build);
6377
assertThat(ex.getMessage(),
6478
containsString("[datafeed_config.job_id] must be set or a [job_config] must be provided"));
79+
80+
requestBuilder
81+
.setJobBuilder(
82+
JobTests.buildJobBuilder(jobId)
83+
.setDatafeed(new DatafeedConfig.Builder(DatafeedConfigTests.createRandomizedDatafeedConfig(jobId)))
84+
)
85+
.setDatafeedId(null)
86+
.setDatafeedBuilder(new DatafeedConfig.Builder());
87+
ex = expectThrows(IllegalArgumentException.class, requestBuilder::build);
88+
assertThat(ex.getMessage(),
89+
containsString("[datafeed_config] must not be present when a [job_config.datafeed_config] is present"));
90+
91+
requestBuilder
92+
.setJobBuilder(JobTests.buildJobBuilder(jobId))
93+
.setDatafeedId(null)
94+
.setDatafeedBuilder(null);
95+
ex = expectThrows(IllegalArgumentException.class, requestBuilder::build);
96+
assertThat(ex.getMessage(),
97+
containsString("[datafeed_config] must be present when a [job_config.datafeed_config] is not present"));
6598
}
6699

67100
@Override

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigBuilderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
public class DatafeedConfigBuilderTests extends AbstractWireSerializingTestCase<DatafeedConfig.Builder> {
3636

37-
static DatafeedConfig.Builder createRandomizedDatafeedConfigBuilder(String jobId, String datafeedId, long bucketSpanMillis) {
37+
public static DatafeedConfig.Builder createRandomizedDatafeedConfigBuilder(String jobId, String datafeedId, long bucketSpanMillis) {
3838
DatafeedConfig.Builder builder = new DatafeedConfig.Builder();
3939
if (jobId != null) {
4040
builder.setJobId(jobId);

x-pack/plugin/ml/qa/ml-with-security/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ tasks.named("yamlRestTest").configure {
196196
'ml/preview_datafeed/Test preview missing datafeed',
197197
'ml/preview_datafeed/Test preview with datafeed_id and job config',
198198
'ml/preview_datafeed/Test preview with datafeed id and config',
199+
'ml/preview_datafeed/Test preview with datafeed config and job config with datafeed config',
199200
'ml/reset_job/Test reset given job is open',
200201
'ml/reset_job/Test reset given unknown job id',
201202
'ml/revert_model_snapshot/Test revert model with invalid snapshotId',

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/preview_datafeed.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,37 @@ setup:
182182
- match: { 3.time: 1487379660000 }
183183
- match: { 3.airline: foo }
184184
- match: { 3.responsetime: 42.0 }
185+
186+
- do:
187+
ml.preview_datafeed:
188+
body: >
189+
{
190+
"job_config": {
191+
"analysis_config": {
192+
"bucket_span": "1h",
193+
"detectors": [{"function":"sum","field_name":"responsetime","by_field_name":"airline"}]
194+
},
195+
"data_description": {
196+
"time_field":"time"
197+
},
198+
"datafeed_config": {
199+
"indexes":"airline-data"
200+
}
201+
}
202+
}
203+
- length: { $body: 4 }
204+
- match: { 0.time: 1487376000000 }
205+
- match: { 0.airline: foo }
206+
- match: { 0.responsetime: 1.0 }
207+
- match: { 1.time: 1487377800000 }
208+
- match: { 1.airline: foo }
209+
- match: { 1.responsetime: 1.0 }
210+
- match: { 2.time: 1487379600000 }
211+
- match: { 2.airline: bar }
212+
- match: { 2.responsetime: 42.0 }
213+
- match: { 3.time: 1487379660000 }
214+
- match: { 3.airline: foo }
215+
- match: { 3.responsetime: 42.0 }
185216
---
186217
"Test preview aggregation datafeed with doc_count":
187218

@@ -313,6 +344,65 @@ setup:
313344
- match: { 2.responsetime: 42.0 }
314345
- match: { 2.doc_count: 1 }
315346

347+
- do:
348+
ml.preview_datafeed:
349+
body: >
350+
{
351+
"job_config": {
352+
"analysis_config" : {
353+
"bucket_span": "1h",
354+
"summary_count_field_name": "doc_count",
355+
"detectors" :[{"function":"sum","field_name":"responsetime","by_field_name":"airline"}]
356+
},
357+
"data_description" : {
358+
"time_field":"time"
359+
},
360+
"datafeed_config": {
361+
"indexes":"airline-data",
362+
"aggregations": {
363+
"buckets": {
364+
"histogram": {
365+
"field": "time",
366+
"interval": 3600000
367+
},
368+
"aggregations": {
369+
"time": {
370+
"max": {
371+
"field": "time"
372+
}
373+
},
374+
"airline": {
375+
"terms": {
376+
"field": "airline",
377+
"size": 100
378+
},
379+
"aggregations": {
380+
"responsetime": {
381+
"sum": {
382+
"field": "responsetime"
383+
}
384+
}
385+
}
386+
}
387+
}
388+
}
389+
}
390+
}
391+
}
392+
}
393+
- length: { $body: 3 }
394+
- match: { 0.time: 1487377800000 }
395+
- match: { 0.airline: foo }
396+
- match: { 0.responsetime: 2.0 }
397+
- match: { 0.doc_count: 2 }
398+
- match: { 1.time: 1487379660000 }
399+
- match: { 1.airline: bar }
400+
- match: { 1.responsetime: 42.0 }
401+
- match: { 1.doc_count: 1 }
402+
- match: { 1.time: 1487379660000 }
403+
- match: { 2.airline: foo }
404+
- match: { 2.responsetime: 42.0 }
405+
- match: { 2.doc_count: 1 }
316406
---
317407
"Test preview single metric aggregation datafeed with different summary count field":
318408

@@ -496,6 +586,30 @@ setup:
496586
}
497587
}
498588
---
589+
"Test preview with datafeed config and job config with datafeed config":
590+
591+
- do:
592+
catch: bad_request
593+
ml.preview_datafeed:
594+
body: >
595+
{
596+
"datafeed_config": {
597+
"indexes":"airline-data"
598+
},
599+
"job_config": {
600+
"analysis_config" : {
601+
"bucket_span": "1h",
602+
"detectors" :[{"function":"sum","field_name":"responsetime","by_field_name":"airline"}]
603+
},
604+
"data_description" : {
605+
"time_field":"time"
606+
},
607+
"datafeed_config": {
608+
"indexes":"airline-data"
609+
}
610+
}
611+
}
612+
---
499613
"Test preview datafeed with unavailable index":
500614

501615
- do:

0 commit comments

Comments
 (0)