Skip to content

Commit

Permalink
wip: create ml anomalies layer in maps
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarezmelissa87 committed Dec 14, 2021
1 parent 7e8b5f9 commit 17dac9b
Show file tree
Hide file tree
Showing 15 changed files with 1,046 additions and 1 deletion.
5 changes: 5 additions & 0 deletions x-pack/plugins/maps/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ export {
AGG_TYPE,
COLOR_MAP_TYPE,
ES_GEO_FIELD_TYPE,
FieldFormatter,
FIELD_ORIGIN,
INITIAL_LOCATION,
LABEL_BORDER_SIZES,
LAYER_TYPE,
LAYER_WIZARD_CATEGORY,
MAP_SAVED_OBJECT_TYPE,
MAX_ZOOM,
MIN_ZOOM,
SOURCE_TYPES,
STYLE_TYPE,
SYMBOLIZE_AS_TYPES,
VECTOR_SHAPE_TYPE,
} from './constants';

export type {
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/ml/common/util/job_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ export function isMappableJob(job: CombinedJob, detectorIndex: number): boolean
return isMappable;
}

// Returns a boolean indicating whether the specified job is suitable for maps plugin.
export function isJobWithGeoData(job: Job): boolean {
let isMappable = false;
const { detectors } = job.analysis_config;

detectors.forEach((detector) => {
if (detector.function === ML_JOB_AGGREGATION.LAT_LONG) {
isMappable = true;
}
});
return isMappable;
}

/**
* Validates that composite definition only have sources that are only terms and date_histogram
* if composite is defined.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export const jobsApiProvider = (httpService: HttpService) => ({
});
},

jobsWithGeo() {
return httpService.http<string[]>({
path: `${ML_BASE_PATH}/jobs/jobs_with_geo`,
method: 'GET',
});
},

jobsWithTimerange(dateFormatTz: string) {
const body = JSON.stringify({ dateFormatTz });
return httpService.http<{
Expand Down
84 changes: 84 additions & 0 deletions x-pack/plugins/ml/public/maps/anomaly_job_selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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 React, { Component } from 'react';

import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEqual } from 'lodash';
import { ml } from '../application/services/ml_api_service';

interface Props {
onJobChange: (jobId: string) => void;
}

interface State {
jobId?: string;
jobIdList?: Array<EuiComboBoxOptionOption<string>>;
}

export class AnomalyJobSelector extends Component<Props, State> {
private _isMounted: boolean = false;

state: State = {};

private async _loadJobs() {
const jobIdList = await ml.jobs.jobsWithGeo();
const options = jobIdList.map((jobId) => {
return { label: jobId, value: jobId };
});
if (this._isMounted && !isEqual(options, this.state.jobIdList)) {
this.setState({
jobIdList: options,
});
}
}

componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any): void {
this._loadJobs();
}

componentDidMount(): void {
this._isMounted = true;
this._loadJobs();
}

componentWillUnmount() {
this._isMounted = false;
}

onJobIdSelect = (selectedOptions: Array<EuiComboBoxOptionOption<string>>) => {
const jobId: string = selectedOptions[0].value!;
if (this._isMounted) {
this.setState({ jobId });
this.props.onJobChange(jobId);
}
};

render() {
if (!this.state.jobIdList) {
return null;
}

const options = this.state.jobId ? [{ value: this.state.jobId, label: this.state.jobId }] : [];
return (
<EuiFormRow
label={i18n.translate('xpack.ml.maps.jobIdLabel', {
defaultMessage: 'JobId',
})}
display="columnCompressed"
>
<EuiComboBox
singleSelection={true}
onChange={this.onJobIdSelect}
options={this.state.jobIdList}
selectedOptions={options}
/>
</EuiFormRow>
);
}
}
72 changes: 72 additions & 0 deletions x-pack/plugins/ml/public/maps/anomaly_layer_wizard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import uuid from 'uuid';
import { LAYER_TYPE, LAYER_WIZARD_CATEGORY, STYLE_TYPE } from '../../../maps/common';
import { AnomalySource, AnomalySourceDescriptor } from './anomaly_source';
import { CreateAnomalySourceEditor } from './create_anomaly_source_editor';
import {
VectorLayerDescriptor,
VectorStylePropertiesDescriptor,
} from '../../../maps/common/descriptor_types';
import type { LayerWizard, RenderWizardArguments } from '../../../maps/public';

export const anomalyLayerWizard: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.SOLUTIONS],
description: i18n.translate('xpack.ml.maps.anomalyLayerDescription', {
defaultMessage: 'Create anomalies layers',
}),
disabledReason: i18n.translate('xpack.ml.maps.anomalyLayerUnavailableMessage', {
defaultMessage:
'Whatever reason the user cannot see ML card (likely because no enterprise license or no ML privileges)',
}),
icon: 'outlierDetectionJob',
getIsDisabled: () => {
// Do enterprise license check
// Do check if user has access to job (namespace chec or whatever)
return false;
},
renderWizard: ({ previewLayers }: RenderWizardArguments) => {
const onSourceConfigChange = (sourceConfig: Partial<AnomalySourceDescriptor> | null) => {
if (!sourceConfig) {
previewLayers([]);
return;
}

// remove usage of VectorLayer.createDescriptor. should be hardcoded to actual descriptor
const anomalyLayerDescriptor: VectorLayerDescriptor = {
id: uuid(),
type: LAYER_TYPE.VECTOR,
sourceDescriptor: AnomalySource.createDescriptor({
jobId: sourceConfig.jobId,
typicalActual: sourceConfig.typicalActual,
}),
style: {
type: 'VECTOR',
properties: {
fillColor: {
type: STYLE_TYPE.STATIC,
options: {
color: 'rgb(255,0,0)',
},
},
} as unknown as VectorStylePropertiesDescriptor,
isTimeAware: false,
},
};

previewLayers([anomalyLayerDescriptor]);
};

return <CreateAnomalySourceEditor onSourceConfigChange={onSourceConfigChange} />;
},
title: i18n.translate('xpack.ml.maps.anomalyLayerTitle', {
defaultMessage: 'ML Anomalies',
}),
};
Loading

0 comments on commit 17dac9b

Please sign in to comment.