Skip to content

Commit

Permalink
[ML] Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
qn895 committed Nov 2, 2020
1 parent 85f1526 commit 21bb6cb
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { PlotByFunctionControls } from './plot_function_controls';
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;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

const plotByFunctionOptions = [
{
value: 'avg',
text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByAvgOptionLabel', {
defaultMessage: 'average',
}),
},
{
value: 'min',
text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMinOptionLabel', {
defaultMessage: 'min',
}),
},
{
value: 'max',
text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMaxOptionLabel', {
defaultMessage: 'max',
}),
},
];
export const PlotByFunctionControls = ({
actualPlotFunction,
setPlotByFunction,
}: {
actualPlotFunction: undefined | string;
setPlotByFunction: (func: string) => void;
}) => {
if (actualPlotFunction === 'unknown' || !actualPlotFunction) return null;
return (
<EuiFlexItem style={{ textAlign: 'right' }} grow={false}>
<EuiFormRow
label={i18n.translate('xpack.ml.timeSeriesExplorer.metricPlotByOption', {
defaultMessage: 'Function',
})}
>
<EuiSelect
options={plotByFunctionOptions}
value={actualPlotFunction ?? 'avg'}
onChange={(e) => setPlotByFunction(e.target.value)}
aria-label="Pick function to plot by (min, max, or average) if metric function"
/>
</EuiFormRow>
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { mlJobService } from '../services/job_service';
export const UNKNOWN_METRIC_PLOT_FUNCTION = 'unknown';

export const getPlotByOptions = (
selectedJobId: string,
selectedDetectorIndex: number,
currentActualPlotFunction: string | undefined,
defaultFunctionToPlotIfMetric: string | undefined
) => {
// only load the options to view chart by 'avg', 'min', or 'max'
// if the original function is 'metric'
const selectedJob = mlJobService.getJob(selectedJobId);

if (selectedJob === undefined) return;

const detectors = selectedJob.analysis_config.detectors;

if (detectors && detectors[selectedDetectorIndex]?.function) {
if (detectors[selectedDetectorIndex]?.function === 'metric') {
if (currentActualPlotFunction === undefined) {
// here we just know the detector is a metric function, but we don't know what to plot yet
// need to find the highest scoring anomaly record in order to pick the default view
return defaultFunctionToPlotIfMetric || UNKNOWN_METRIC_PLOT_FUNCTION;
}
} else {
return undefined;
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ import {
import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/settings';
import { getControlsForDetector } from './get_controls_for_detector';
import { SeriesControls } from './components/series_controls';
import { PlotByFunctionControls } from './components/plot_function_controls';
import { getPlotByOptions, UNKNOWN_METRIC_PLOT_FUNCTION } from './get_plot_by_options';
import { getToastNotificationService } from '../services/toast_notification_service';

const UNKNOWN_METRIC_PLOT_FUNCTION = 'unknown';
// Used to indicate the chart is being plotted across
// all partition field values, where the cardinality of the field cannot be
// obtained as it is not aggregatable e.g. 'all distinct kpi_indicator values'
Expand Down Expand Up @@ -144,6 +146,7 @@ function getTimeseriesexplorerDefaultState() {
zoomToFocusLoaded: undefined,
// Sets function to plot by if original function is metric
actualPlotFunction: undefined,
defaultFunctionToPlotIfMetric: undefined,
};
}

Expand Down Expand Up @@ -392,29 +395,18 @@ export class TimeSeriesExplorer extends React.Component {
);
};

loadPlotByOptions = async () => {
// only load the options to view chart by 'avg', 'min', or 'max'
// if the original function is 'metric'
getPlotByOptions = () => {
const { selectedJobId, selectedDetectorIndex } = this.props;
const selectedJob = mlJobService.getJob(selectedJobId);

if (selectedJob === undefined) return;

const detectors = selectedJob.analysis_config.detectors;

if (detectors && detectors[selectedDetectorIndex]?.function) {
if (detectors[selectedDetectorIndex]?.function === 'metric') {
if (this.state.actualPlotFunction === undefined) {
// here we just know the detector is a metric function, but we don't know what to plot yet
// need to find the highest scoring anomaly record in order to pick the default view
this.setState({ actualPlotFunction: UNKNOWN_METRIC_PLOT_FUNCTION });
}
} else {
this.setState({ actualPlotFunction: undefined });
}
}
const { actualPlotFunction, defaultFunctionToPlotIfMetric } = this.state;
this.setPlotByFunction(
getPlotByOptions(
selectedJobId,
selectedDetectorIndex,
actualPlotFunction,
defaultFunctionToPlotIfMetric
)
);
};

/**
* Loads available entity values.
* @param {Array} entities - Entity controls configuration
Expand Down Expand Up @@ -805,13 +797,19 @@ export class TimeSeriesExplorer extends React.Component {
.then((resp) => {
if (Array.isArray(resp?.records) && resp.records.length === 1) {
const highestScoringAnomaly = resp.records[0];
this.setState({ actualPlotFunction: highestScoringAnomaly.function_description });
const defaultFunctionToPlotIfMetric = mlFunctionToESAggregation(
highestScoringAnomaly?.function_description
);
this.setState({ defaultFunctionToPlotIfMetric });
this.setPlotByFunction(defaultFunctionToPlotIfMetric);
}
})
.catch((resp) => {
console.log(
'Time series explorer - error getting record with highest anomaly score:',
resp
.catch((error) => {
getToastNotificationService().displayErrorToast(
error,
i18n.translate('xpack.ml.timeSeriesExplorer.highestAnomalyScoreErrorToastTitle', {
defaultMessage: 'An error occurred getting record with the highest anomaly score',
})
);
});
} else {
Expand Down Expand Up @@ -956,7 +954,7 @@ export class TimeSeriesExplorer extends React.Component {
) {
const entityControls = this.getControlsForDetector();
this.loadEntityValues(entityControls);
this.loadPlotByOptions();
this.getPlotByOptions();
}

if (
Expand Down Expand Up @@ -1143,6 +1141,13 @@ export class TimeSeriesExplorer extends React.Component {
selectedEntities={this.props.selectedEntities}
bounds={bounds}
>
{actualPlotFunction && (
<PlotByFunctionControls
actualPlotFunction={actualPlotFunction}
setPlotByFunction={this.setPlotByFunction}
/>
)}

{arePartitioningFieldsProvided && (
<EuiFlexItem style={{ textAlign: 'right' }}>
<EuiFormRow hasEmptyLabelSpace style={{ maxWidth: '100%' }}>
Expand All @@ -1157,7 +1162,6 @@ export class TimeSeriesExplorer extends React.Component {
</EuiFlexItem>
)}
</SeriesControls>

<EuiSpacer size="m" />

{fullRefresh && loading === true && (
Expand Down

0 comments on commit 21bb6cb

Please sign in to comment.