Skip to content

Commit a1a256d

Browse files
authored
[ML] Reactive time-range selection in SMV (#51008)
* [ML] http service to TS, add httpCall using fromFetch * [ML] types, add esSearchRx * [ML] timeresiesexplorer_contans to ts * [ML] timeseries_search_service to TS, add getMetricDataRx * [ML] result service with observables * [ML] update resolvers, forecast data support * [ML] wip timeseriesexplorer * [ML] fix state update for zoom * [ML] skip loading update * [ML] cleanup contextChartSelected * [ML] add to subscriptions * [ML] update imports * [ML] timeseriesexplorer_utils * [ML] refactor result service * [ML] getAnnotations * [ML] rename subject * [ML] fix explorer and unit tests * [ML] fix forecast * [ML] replace skipWhilte with filter * [ML] rename http$ * [ML] rename esSearch$ * [ML] remove filter operator, check for contextChartData before calculating the default range * [ML] remove casting for FocusData * [ML] replace with an arrow function * [ML] fix Job import path * [ML] fix annotations
1 parent 59affba commit a1a256d

File tree

29 files changed

+1429
-1157
lines changed

29 files changed

+1429
-1157
lines changed

x-pack/legacy/plugins/ml/common/util/job_utils.d.ts

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

7+
import { Job } from '../../public/application/jobs/new_job/common/job_creator/configs';
8+
79
export interface ValidationMessage {
810
id: string;
911
}
@@ -39,3 +41,5 @@ export function validateModelMemoryLimitUnits(
3941
export function processCreatedBy(customSettings: { created_by?: string }): void;
4042

4143
export function mlFunctionToESAggregation(functionName: string): string | null;
44+
45+
export function isModelPlotEnabled(job: Job, detectorIndex: number, entityFields: any[]): boolean;

x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const AnnotationsTable = injectI18n(class AnnotationsTable extends Component {
8787
earliestMs: null,
8888
latestMs: null,
8989
maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE
90-
}).then((resp) => {
90+
}).toPromise().then((resp) => {
9191
this.setState((prevState, props) => ({
9292
annotations: resp.annotations[props.jobs[0].job_id] || [],
9393
errorMessage: undefined,

x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.test.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ jest.mock('../../../services/job_service', () => ({
2424
}
2525
}));
2626

27-
jest.mock('../../../services/ml_api_service', () => ({
28-
ml: {
29-
annotations: {
30-
getAnnotations: jest.fn().mockResolvedValue({ annotations: [] })
27+
jest.mock('../../../services/ml_api_service', () => {
28+
const { of } = require('rxjs');
29+
const mockAnnotations$ = of({ annotations: [] });
30+
return {
31+
ml: {
32+
annotations: {
33+
getAnnotations: jest.fn().mockReturnValue(mockAnnotations$)
34+
}
3135
}
32-
}
33-
}));
36+
};}
37+
);
3438

3539
describe('AnnotationsTable', () => {
3640
test('Minimal initialization without props.', () => {

x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export function explorerChartsContainerServiceFactory(callback) {
124124
range.min,
125125
range.max,
126126
config.interval
127-
);
127+
).toPromise();
128128
} else {
129129
// Extract the partition, by, over fields on which to filter.
130130
const criteriaFields = [];
@@ -169,7 +169,7 @@ export function explorerChartsContainerServiceFactory(callback) {
169169
range.min,
170170
range.max,
171171
interval
172-
)
172+
).toPromise()
173173
.then((resp) => {
174174
// Return data in format required by the explorer charts.
175175
const results = resp.results;
@@ -201,7 +201,7 @@ export function explorerChartsContainerServiceFactory(callback) {
201201
range.min,
202202
range.max,
203203
ANOMALIES_MAX_RESULTS
204-
);
204+
).toPromise();
205205
}
206206

207207
// Query 3 - load any scheduled events for the job.
@@ -213,7 +213,7 @@ export function explorerChartsContainerServiceFactory(callback) {
213213
config.interval,
214214
1,
215215
MAX_SCHEDULED_EVENTS
216-
);
216+
).toPromise();
217217
}
218218

219219
// Query 4 - load context data distribution

x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,39 @@ jest.mock('../../services/job_service', () => ({
4747
}
4848
}));
4949

50-
jest.mock('../../services/results_service', () => ({
51-
mlResultsService: {
52-
getMetricData(indices) {
50+
jest.mock('../../services/results_service', () => {
51+
const { of } = require('rxjs');
52+
return {
53+
mlResultsService: {
54+
getMetricData(indices) {
5355
// this is for 'call anomalyChangeListener with actual series config'
54-
if (indices[0] === 'farequote-2017') {
55-
return Promise.resolve(mockSeriesPromisesResponse[0][0]);
56-
}
57-
// this is for 'filtering should skip values of null'
58-
return Promise.resolve(mockMetricClone);
59-
},
60-
getRecordsForCriteria() {
61-
return Promise.resolve(mockSeriesPromisesResponse[0][1]);
62-
},
63-
getScheduledEventsByBucket() {
64-
return Promise.resolve(mockSeriesPromisesResponse[0][2]);
65-
},
66-
getEventDistributionData(indices) {
56+
if (indices[0] === 'farequote-2017') {
57+
return of(mockSeriesPromisesResponse[0][0]);
58+
}
59+
// this is for 'filtering should skip values of null'
60+
return of(mockMetricClone);
61+
},
62+
getRecordsForCriteria() {
63+
return of(mockSeriesPromisesResponse[0][1]);
64+
},
65+
getScheduledEventsByBucket() {
66+
return of(mockSeriesPromisesResponse[0][2]);
67+
},
68+
getEventDistributionData(indices) {
6769
// this is for 'call anomalyChangeListener with actual series config'
68-
if (indices[0] === 'farequote-2017') {
69-
return Promise.resolve([]);
70+
if (indices[0] === 'farequote-2017') {
71+
return Promise.resolve([]);
72+
}
73+
// this is for 'filtering should skip values of null' and
74+
// resolves with a dummy object to trigger the processing
75+
// of the event distribution chartdata filtering
76+
return Promise.resolve([{
77+
entity: 'mock'
78+
}]);
7079
}
71-
// this is for 'filtering should skip values of null' and
72-
// resolves with a dummy object to trigger the processing
73-
// of the event distribution chartdata filtering
74-
return Promise.resolve([{
75-
entity: 'mock'
76-
}]);
7780
}
78-
}
79-
}));
81+
};
82+
});
8083

8184
jest.mock('../../util/string_utils', () => ({
8285
mlEscape(d) { return d; }

x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,
417417
earliestMs: timeRange.earliestMs,
418418
latestMs: timeRange.latestMs,
419419
maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE
420-
}).then((resp) => {
420+
}).toPromise().then((resp) => {
421421
if (resp.error !== undefined || resp.annotations === undefined) {
422422
return resolve([]);
423423
}
@@ -477,7 +477,7 @@ export async function loadAnomaliesTableData(
477477
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
478478
MAX_CATEGORY_EXAMPLES,
479479
influencersFilterQuery
480-
).then((resp) => {
480+
).toPromise().then((resp) => {
481481
const anomalies = resp.anomalies;
482482
const detectorsByJob = mlJobService.detectorsByJob;
483483
anomalies.forEach((anomaly) => {

x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,17 @@ export class ResultsLoader {
150150
if (agg === null) {
151151
return { [dtrIndex]: [emptyModelItem] };
152152
}
153-
const resp = await mlResultsService.getModelPlotOutput(
154-
this._jobCreator.jobId,
155-
dtrIndex,
156-
[],
157-
this._lastModelTimeStamp,
158-
this._jobCreator.end,
159-
`${this._chartInterval.getInterval().asMilliseconds()}ms`,
160-
agg.mlModelPlotAgg
161-
);
153+
const resp = await mlResultsService
154+
.getModelPlotOutput(
155+
this._jobCreator.jobId,
156+
dtrIndex,
157+
[],
158+
this._lastModelTimeStamp,
159+
this._jobCreator.end,
160+
`${this._chartInterval.getInterval().asMilliseconds()}ms`,
161+
agg.mlModelPlotAgg
162+
)
163+
.toPromise();
162164

163165
return this._createModel(resp, dtrIndex);
164166
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 { Observable } from 'rxjs';
8+
import { Job } from '../jobs/new_job/common/job_creator/configs';
9+
10+
export interface ForecastData {
11+
success: boolean;
12+
results: any;
13+
}
14+
15+
export const mlForecastService: {
16+
getForecastData: (
17+
job: Job,
18+
detectorIndex: number,
19+
forecastId: string,
20+
entityFields: any[],
21+
earliestMs: number,
22+
latestMs: number,
23+
interval: string,
24+
aggType: any
25+
) => Observable<ForecastData>;
26+
};

0 commit comments

Comments
 (0)