Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps][ML] Integration follow up: adds partition field info to map point tooltip if available #123516

Merged
merged 5 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
11 changes: 7 additions & 4 deletions x-pack/plugins/ml/public/maps/anomaly_source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import type { SourceEditorArgs } from '../../../maps/public';
import type { DataRequest } from '../../../maps/public';
import type { IVectorSource, SourceStatus } from '../../../maps/public';
import { ML_ANOMALY } from './anomaly_source_factory';
import { getResultsForJobId, MlAnomalyLayers } from './util';
import { getResultsForJobId, ML_ANOMALY_LAYERS } from './util';
import { UpdateAnomalySourceEditor } from './update_anomaly_source_editor';
import type { MlApiServices } from '../application/services/ml_api_service';

export interface AnomalySourceDescriptor extends AbstractSourceDescriptor {
jobId: string;
typicalActual: MlAnomalyLayers;
typicalActual: ML_ANOMALY_LAYERS;
}

export class AnomalySource implements IVectorSource {
Expand All @@ -50,7 +50,7 @@ export class AnomalySource implements IVectorSource {
return {
type: ML_ANOMALY,
jobId: descriptor.jobId,
typicalActual: descriptor.typicalActual || 'actual',
typicalActual: descriptor.typicalActual || ML_ANOMALY_LAYERS.ACTUAL,
};
}

Expand Down Expand Up @@ -232,7 +232,7 @@ export class AnomalySource implements IVectorSource {
}

async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPE[]> {
return this._descriptor.typicalActual === 'connected'
return this._descriptor.typicalActual === 'typical to actual'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this be ML_ANOMALY_LAYERS.TYPICAL_TO_ACTUAL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 1ae5ddc

? [VECTOR_SHAPE_TYPE.LINE]
: [VECTOR_SHAPE_TYPE.POINT];
}
Expand All @@ -251,6 +251,9 @@ export class AnomalySource implements IVectorSource {
const label = ANOMALY_SOURCE_FIELDS[key]?.label;
if (label) {
tooltipProperties.push(new AnomalySourceTooltipProperty(label, properties[key]));
} else if (!ANOMALY_SOURCE_FIELDS[key]) {
// partition field keys will be different each time so won't be in ANOMALY_SOURCE_FIELDS
tooltipProperties.push(new AnomalySourceTooltipProperty(key, properties[key]));
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions x-pack/plugins/ml/public/maps/anomaly_source_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,58 @@ export const ANOMALY_SOURCE_FIELDS: Record<string, Record<string, string>> = {
}),
type: 'string',
},
// this value is only used to place the point on the map
actual: {},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: suggestion to add a comment to clarify what's the difference between actual and actualDisplay here or why this actual here doesn't need a label.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2321384

actualDisplay: {
label: i18n.translate('xpack.ml.maps.anomalyLayerActualLabel', {
defaultMessage: 'Actual',
}),
type: 'string',
},
// this value is only used to place the point on the map
typical: {},
typicalDisplay: {
label: i18n.translate('xpack.ml.maps.anomalyLayerTypicalLabel', {
defaultMessage: 'Typical',
}),
type: 'string',
},
partition_field_name: {
label: i18n.translate('xpack.ml.maps.anomalyLayerPartitionFieldNameLabel', {
defaultMessage: 'Partition field name',
}),
type: 'string',
},
partition_field_value: {
label: i18n.translate('xpack.ml.maps.anomalyLayerPartitionFieldValueLabel', {
defaultMessage: 'Partition field value',
}),
type: 'string',
},
by_field_name: {
label: i18n.translate('xpack.ml.maps.anomalyLayerByFieldNameLabel', {
defaultMessage: 'By field name',
}),
type: 'string',
},
by_field_value: {
label: i18n.translate('xpack.ml.maps.anomalyLayerByFieldValueLabel', {
defaultMessage: 'By field value',
}),
type: 'string',
},
over_field_name: {
label: i18n.translate('xpack.ml.maps.anomalyLayerOverFieldNameLabel', {
defaultMessage: 'Over field name',
}),
type: 'string',
},
over_field_value: {
label: i18n.translate('xpack.ml.maps.anomalyLayerOverFieldValueLabel', {
defaultMessage: 'Over field value',
}),
type: 'string',
},
};

export class AnomalySourceTooltipProperty implements ITooltipProperty {
Expand Down
10 changes: 5 additions & 5 deletions x-pack/plugins/ml/public/maps/create_anomaly_source_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EuiPanel } from '@elastic/eui';
import { AnomalySourceDescriptor } from './anomaly_source';
import { AnomalyJobSelector } from './anomaly_job_selector';
import { LayerSelector } from './layer_selector';
import { MlAnomalyLayers } from './util';
import { ML_ANOMALY_LAYERS } from './util';
import type { MlApiServices } from '../application/services/ml_api_service';

interface Props {
Expand All @@ -21,7 +21,7 @@ interface Props {

interface State {
jobId?: string;
typicalActual?: MlAnomalyLayers;
typicalActual?: ML_ANOMALY_LAYERS;
}

export class CreateAnomalySourceEditor extends Component<Props, State> {
Expand All @@ -32,7 +32,7 @@ export class CreateAnomalySourceEditor extends Component<Props, State> {
if (this.state.jobId) {
this.props.onSourceConfigChange({
jobId: this.state.jobId,
typicalActual: this.state.typicalActual,
typicalActual: this.state.typicalActual || ML_ANOMALY_LAYERS.ACTUAL,
});
}
}
Expand All @@ -41,7 +41,7 @@ export class CreateAnomalySourceEditor extends Component<Props, State> {
this._isMounted = true;
}

private onTypicalActualChange = (typicalActual: MlAnomalyLayers) => {
private onTypicalActualChange = (typicalActual: ML_ANOMALY_LAYERS) => {
if (!this._isMounted) {
return;
}
Expand Down Expand Up @@ -73,7 +73,7 @@ export class CreateAnomalySourceEditor extends Component<Props, State> {
const selector = this.state.jobId ? (
<LayerSelector
onChange={this.onTypicalActualChange}
typicalActual={this.state.typicalActual || 'actual'}
typicalActual={this.state.typicalActual || ML_ANOMALY_LAYERS.ACTUAL}
/>
) : null;
return (
Expand Down
32 changes: 22 additions & 10 deletions x-pack/plugins/ml/public/maps/layer_selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import React, { Component } from 'react';

import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { MlAnomalyLayers } from './util';
import { ML_ANOMALY_LAYERS } from './util';

interface Props {
onChange: (typicalActual: MlAnomalyLayers) => void;
typicalActual: MlAnomalyLayers;
onChange: (typicalActual: ML_ANOMALY_LAYERS) => void;
typicalActual: ML_ANOMALY_LAYERS;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand All @@ -33,10 +33,7 @@ export class LayerSelector extends Component<Props, State> {
}

onSelect = (selectedOptions: Array<EuiComboBoxOptionOption<string>>) => {
const typicalActual: MlAnomalyLayers = selectedOptions[0].value! as
| 'typical'
| 'actual'
| 'connected';
const typicalActual: ML_ANOMALY_LAYERS = selectedOptions[0].value! as ML_ANOMALY_LAYERS;
if (this._isMounted) {
this.setState({ typicalActual });
this.props.onChange(typicalActual);
Expand All @@ -56,9 +53,24 @@ export class LayerSelector extends Component<Props, State> {
singleSelection={true}
onChange={this.onSelect}
options={[
{ value: 'actual', label: 'actual' },
{ value: 'typical', label: 'typical' },
{ value: 'connected', label: 'connected' },
{
value: ML_ANOMALY_LAYERS.ACTUAL,
label: i18n.translate('xpack.ml.maps.actualLabel', {
defaultMessage: ML_ANOMALY_LAYERS.ACTUAL,
}),
},
{
value: ML_ANOMALY_LAYERS.TYPICAL,
label: i18n.translate('xpack.ml.maps.typicalLabel', {
defaultMessage: ML_ANOMALY_LAYERS.TYPICAL,
}),
},
{
value: ML_ANOMALY_LAYERS.TYPICAL_TO_ACTUAL,
label: i18n.translate('xpack.ml.maps.typicalToActualLabel', {
defaultMessage: ML_ANOMALY_LAYERS.TYPICAL_TO_ACTUAL,
}),
},
]}
selectedOptions={options}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import React, { Fragment, Component } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { LayerSelector } from './layer_selector';
import { MlAnomalyLayers } from './util';
import { ML_ANOMALY_LAYERS } from './util';

interface Props {
onChange: (...args: Array<{ propName: string; value: unknown }>) => void;
typicalActual: MlAnomalyLayers;
typicalActual: ML_ANOMALY_LAYERS;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand All @@ -34,7 +34,7 @@ export class UpdateAnomalySourceEditor extends Component<Props, State> {
</EuiTitle>
<EuiSpacer size="s" />
<LayerSelector
onChange={(typicalActual: MlAnomalyLayers) => {
onChange={(typicalActual: ML_ANOMALY_LAYERS) => {
this.props.onChange({
propName: 'typicalActual',
value: typicalActual,
Expand Down
94 changes: 42 additions & 52 deletions x-pack/plugins/ml/public/maps/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import type { MlApiServices } from '../application/services/ml_api_service';
import { MLAnomalyDoc } from '../../common/types/anomalies';
import { VectorSourceRequestMeta } from '../../../maps/common';

export type MlAnomalyLayers = 'typical' | 'actual' | 'connected';
export type MlAnomalyLayers = 'typical' | 'actual' | 'typical to actual';
export enum ML_ANOMALY_LAYERS {
TYPICAL = 'typical',
ACTUAL = 'actual',
TYPICAL_TO_ACTUAL = 'typical to actual',
}

// Must reverse coordinates here. Map expects [lon, lat] - anomalies are stored as [lat, lon] for lat_lon jobs
function getCoordinates(actualCoordinateStr: string, round: boolean = false): number[] {
Expand Down Expand Up @@ -64,16 +69,6 @@ export async function getResultsForJobId(
}

let resp: ESSearchResponse<MLAnomalyDoc> | null = null;
let hits: Array<{
actual: number[];
actualDisplay: number[];
fieldName?: string;
functionDescription: string;
typical: number[];
typicalDisplay: number[];
record_score: number;
timestamp: string;
}> = [];

try {
resp = await mlResultsService.anomalySearch(
Expand All @@ -86,8 +81,9 @@ export async function getResultsForJobId(
// search may fail if the job doesn't already exist
// ignore this error as the outer function call will raise a toast
}
if (resp !== null && resp.hits.total.value > 0) {
hits = resp.hits.hits.map(({ _source }) => {

const features: Feature[] =
resp?.hits.hits.map(({ _source }) => {
const geoResults = _source.geo_results;
const actualCoordStr = geoResults && geoResults.actual_point;
const typicalCoordStr = geoResults && geoResults.typical_point;
Expand All @@ -104,47 +100,41 @@ export async function getResultsForJobId(
typical = getCoordinates(typicalCoordStr);
typicalDisplay = getCoordinates(typicalCoordStr, true);
}
return {
fieldName: _source.field_name,
functionDescription: _source.function_description,
timestamp: formatHumanReadableDateTimeSeconds(_source.timestamp),
typical,
typicalDisplay,
actual,
actualDisplay,
record_score: Math.floor(_source.record_score),
};
});
}

const features: Feature[] = hits.map((result) => {
let geometry: Geometry;
if (locationType === 'typical' || locationType === 'actual') {
geometry = {
type: 'Point',
coordinates: locationType === 'typical' ? result.typical : result.actual,
};
} else {
geometry = {
type: 'LineString',
coordinates: [result.typical, result.actual],
let geometry: Geometry;
if (locationType === 'typical' || locationType === 'actual') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we can use the new ML_ANOMALY_LAYERS here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep - missed it! thanks - 1ae5ddc

geometry = {
type: 'Point',
coordinates: locationType === 'typical' ? typical : actual,
};
} else {
geometry = {
type: 'LineString',
coordinates: [typical, actual],
};
}
return {
type: 'Feature',
geometry,
properties: {
actual,
actualDisplay,
typical,
typicalDisplay,
fieldName: _source.field_name,
functionDescription: _source.function_description,
timestamp: formatHumanReadableDateTimeSeconds(_source.timestamp),
record_score: Math.floor(_source.record_score),
...(_source.partition_field_name
? { [_source.partition_field_name]: _source.partition_field_value }
: {}),
...(_source.by_field_name ? { [_source.by_field_name]: _source.by_field_value } : {}),
...(_source.over_field_name
? { [_source.over_field_name]: _source.over_field_value }
: {}),
},
};
}
return {
type: 'Feature',
geometry,
properties: {
actual: result.actual,
actualDisplay: result.actualDisplay,
typical: result.typical,
typicalDisplay: result.typicalDisplay,
fieldName: result.fieldName,
functionDescription: result.functionDescription,
timestamp: result.timestamp,
record_score: result.record_score,
},
};
});
}) || [];

return {
type: 'FeatureCollection',
Expand Down