Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/common/constants/new_job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum JOB_TYPE {
ADVANCED = 'advanced',
CATEGORIZATION = 'categorization',
RARE = 'rare',
GEO = 'geo',
}

export enum CREATED_BY_LABEL {
Expand All @@ -20,6 +21,7 @@ export enum CREATED_BY_LABEL {
POPULATION = 'population-wizard',
CATEGORIZATION = 'categorization-wizard',
RARE = 'rare-wizard',
GEO = 'geo-wizard',
APM_TRANSACTION = 'ml-module-apm-transaction',
SINGLE_METRIC_FROM_LENS = 'single-metric-wizard-from-lens',
MULTI_METRIC_FROM_LENS = 'multi-metric-wizard-from-lens',
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/common/util/fields_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function getNumericalFields(fields: Field[]): Field[] {
);
}

function getGeoFields(fields: Field[]): Field[] {
export function getGeoFields(fields: Field[]): Field[] {
return fields.filter(
(f) => f.type === ES_FIELD_TYPES.GEO_POINT || f.type === ES_FIELD_TYPES.GEO_SHAPE
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function getAnomalyFeatures(
},
properties: {
record_score: Math.floor(anomaly.record_score),
[type]: coordinates.map((point: number) => point.toFixed(2)),
[type]: coordinates.map((point: number) => Number(point.toFixed(2))),
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';
import type { DataView } from '@kbn/data-views-plugin/common';
import { IndexPatternTitle } from '../../../../../../common/types/kibana';
import { IndicesOptions } from '../../../../../../common/types/anomaly_detection_jobs';
import {
Field,
Expand All @@ -32,7 +31,7 @@ export type LineChartData = Record<DetectorIndex, LineChartPoint[]>;
const eq = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs);

export class ChartLoader {
private _indexPatternTitle: IndexPatternTitle = '';
protected _dataView: DataView;
private _timeFieldName: string = '';
private _query: object = {};

Expand All @@ -42,7 +41,7 @@ export class ChartLoader {
private _getCategoryFields = memoizeOne(getCategoryFieldsOrig, eq);

constructor(indexPattern: DataView, query: object) {
this._indexPatternTitle = indexPattern.title;
this._dataView = indexPattern;
this._query = query;

if (typeof indexPattern.timeFieldName === 'string') {
Expand Down Expand Up @@ -70,7 +69,7 @@ export class ChartLoader {
const aggFieldPairNames = aggFieldPairs.map(getAggFieldPairNames);

const resp = await this._newJobLineChart(
this._indexPatternTitle,
this._dataView.getIndexPattern(),
this._timeFieldName,
start,
end,
Expand Down Expand Up @@ -107,7 +106,7 @@ export class ChartLoader {
const aggFieldPairNames = aggFieldPairs.map(getAggFieldPairNames);

const resp = await this._newJobPopulationsChart(
this._indexPatternTitle,
this._dataView.getIndexPattern(),
this._timeFieldName,
start,
end,
Expand All @@ -133,7 +132,7 @@ export class ChartLoader {
): Promise<LineChartPoint[]> {
if (this._timeFieldName !== '') {
const resp = await this._getEventRateData(
this._indexPatternTitle,
this._dataView.getIndexPattern(),
this._query,
this._timeFieldName,
start,
Expand All @@ -160,7 +159,7 @@ export class ChartLoader {
indicesOptions?: IndicesOptions
): Promise<string[]> {
const { results } = await this._getCategoryFields(
this._indexPatternTitle,
this._dataView.getIndexPattern(),
field.name,
10,
this._query,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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 type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import { JobCreator } from './job_creator';
import {
Field,
Aggregation,
SplitField,
AggFieldPair,
} from '../../../../../../common/types/fields';
import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs';
import { createBasicDetector } from './util/default_configs';
import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job';
import { getRichDetectors } from './util/general';
import { isSparseDataJob } from './util/general';

export class GeoJobCreator extends JobCreator {
private _geoField: Field | null = null;
private _geoAgg: Aggregation | null = null;
// set partitionField as the default split field for geo jobs
private _splitField: SplitField = null;

protected _type: JOB_TYPE = JOB_TYPE.GEO;

constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.GEO;
this._wizardInitialized$.next(true);
}

public setDefaultDetectorProperties(geo: Aggregation | null) {
if (geo === null) {
throw Error('lat_long aggregations missing');
}
this._geoAgg = geo;
}

public get geoField() {
return this._geoField;
}

public get geoAgg() {
return this._geoAgg;
}

public setGeoField(field: Field | null) {
this._geoField = field;

if (field === null) {
this.removeSplitField();
this._removeDetector(0);
this._detectors.length = 0;
this._fields.length = 0;
return;
}

const agg = this._geoAgg!;

this.removeAllDetectors();
const dtr = this._createDetector(agg, field);
this._addDetector(dtr, agg, field);
}

// set the split field
public setSplitField(field: SplitField) {
this._splitField = field;

if (this._splitField === null) {
this.removeSplitField();
} else {
for (let i = 0; i < this._detectors.length; i++) {
this._detectors[i].partition_field_name = this._splitField.id;
}
}
}

public removeSplitField() {
this._detectors.forEach((d) => {
delete d.partition_field_name;
});
}

public get splitField(): SplitField {
return this._splitField;
}

// create a new detector object, applying the overall split field
private _createDetector(agg: Aggregation, field: Field) {
const dtr: Detector = createBasicDetector(agg, field);

if (this._splitField !== null) {
dtr.partition_field_name = this._splitField.id;
}
return dtr;
}

public get aggFieldPairs(): AggFieldPair[] {
return this.detectors.map((d, i) => ({
field: this._fields[i],
agg: this._aggs[i],
}));
}

public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.GEO;
this._sparseData = isSparseDataJob(job, datafeed);
const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);

this.removeSplitField();
this.removeAllDetectors();
this.removeAllDetectors();

if (detectors.length) {
this.setGeoField(detectors[0].field);
if (detectors[0].partitionField !== null) {
this.setSplitField(detectors[0].partitionField);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { PopulationJobCreator } from './population_job_creator';
export { AdvancedJobCreator } from './advanced_job_creator';
export { CategorizationJobCreator } from './categorization_job_creator';
export { RareJobCreator } from './rare_job_creator';
export { GeoJobCreator } from './geo_job_creator';
export type { JobCreatorType } from './type_guards';
export {
isSingleMetricJobCreator,
Expand All @@ -20,5 +21,6 @@ export {
isAdvancedJobCreator,
isCategorizationJobCreator,
isRareJobCreator,
isGeoJobCreator,
} from './type_guards';
export { jobCreatorFactory } from './job_creator_factory';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PopulationJobCreator } from './population_job_creator';
import { AdvancedJobCreator } from './advanced_job_creator';
import { CategorizationJobCreator } from './categorization_job_creator';
import { RareJobCreator } from './rare_job_creator';
import { GeoJobCreator } from './geo_job_creator';

import { JOB_TYPE } from '../../../../../../common/constants/new_job';

Expand All @@ -39,6 +40,9 @@ export const jobCreatorFactory =
case JOB_TYPE.RARE:
jc = RareJobCreator;
break;
case JOB_TYPE.GEO:
jc = GeoJobCreator;
break;
default:
jc = SingleMetricJobCreator;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PopulationJobCreator } from './population_job_creator';
import { AdvancedJobCreator } from './advanced_job_creator';
import { CategorizationJobCreator } from './categorization_job_creator';
import { RareJobCreator } from './rare_job_creator';
import { GeoJobCreator } from './geo_job_creator';
import { JOB_TYPE } from '../../../../../../common/constants/new_job';

export type JobCreatorType =
Expand All @@ -19,7 +20,8 @@ export type JobCreatorType =
| PopulationJobCreator
| AdvancedJobCreator
| CategorizationJobCreator
| RareJobCreator;
| RareJobCreator
| GeoJobCreator;

export function isSingleMetricJobCreator(
jobCreator: JobCreatorType
Expand Down Expand Up @@ -52,3 +54,7 @@ export function isCategorizationJobCreator(
export function isRareJobCreator(jobCreator: JobCreatorType): jobCreator is RareJobCreator {
return jobCreator.type === JOB_TYPE.RARE;
}

export function isGeoJobCreator(jobCreator: JobCreatorType): jobCreator is GeoJobCreator {
return jobCreator.type === JOB_TYPE.GEO;
}
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ export function getJobCreatorTitle(jobCreator: JobCreatorType) {
return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.rare', {
defaultMessage: 'Rare',
});
case JOB_TYPE.GEO:
return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.geo', {
defaultMessage: 'Geo',
});
default:
return '';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { MapLoader } from './map_loader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';
import type { DataView } from '@kbn/data-views-plugin/common';
import { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common';
import type { MapsStartApi } from '@kbn/maps-plugin/public';
import { ChartLoader } from '../chart_loader';
import { Field, SplitField } from '../../../../../../common/types/fields';
const eq = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs);

export class MapLoader extends ChartLoader {
private _getMapData;

constructor(indexPattern: DataView, query: object, mapsPlugin: MapsStartApi | undefined) {
super(indexPattern, query);

this._getMapData = mapsPlugin
? memoizeOne(mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor, eq)
: null;
}

async getMapLayersForGeoJob(
geoField: Field,
splitField: SplitField,
fieldValues: string[],
filters?: any[]
) {
const layerList: LayerDescriptor[] = [];
if (this._dataView.id !== undefined && geoField) {
const params: any = {
indexPatternId: this._dataView.id,
geoFieldName: geoField.name,
geoFieldType: geoField.type as unknown as ES_GEO_FIELD_TYPE,
filters: filters ?? [],
...(fieldValues.length && splitField
? { query: { query: `${splitField.name}:${fieldValues[0]}`, language: 'kuery' } }
: {}),
};

const searchLayerDescriptor = this._getMapData ? await this._getMapData(params) : null;

if (searchLayerDescriptor) {
layerList.push(searchLayerDescriptor);
}
}
return layerList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Field, Aggregation } from '../../../../../../common/types/fields';
import { TimeBuckets } from '../../../../util/time_buckets';
import { JobCreatorType, SingleMetricJobCreator } from '../../common/job_creator';
import { ChartLoader } from '../../common/chart_loader';
import { MapLoader } from '../../common/map_loader';
import { ResultsLoader } from '../../common/results_loader';
import { JobValidator } from '../../common/job_validator';
import { ExistingJobsAndGroups } from '../../../../services/job_service';
Expand All @@ -19,6 +20,7 @@ export interface JobCreatorContextValue {
jobCreatorUpdate: () => void;
jobCreator: JobCreatorType;
chartLoader: ChartLoader;
mapLoader: MapLoader;
resultsLoader: ResultsLoader;
chartInterval: TimeBuckets;
jobValidator: JobValidator;
Expand All @@ -33,6 +35,7 @@ export const JobCreatorContext = createContext<JobCreatorContextValue>({
jobCreatorUpdate: () => {},
jobCreator: {} as SingleMetricJobCreator,
chartLoader: {} as ChartLoader,
mapLoader: {} as MapLoader,
resultsLoader: {} as ResultsLoader,
chartInterval: {} as TimeBuckets,
jobValidator: {} as JobValidator,
Expand Down
Loading