Skip to content

Commit

Permalink
[ML] Plot function associated with highest anomaly score by default
Browse files Browse the repository at this point in the history
  • Loading branch information
qn895 committed Oct 26, 2020
1 parent 26aa395 commit ffce815
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,22 @@ export class ExplorerChartSingleMetric extends React.Component {
});
}

if (marker.metricActualPlotFunction !== undefined) {
tooltipData.push({
label: i18n.translate(
'xpack.ml.explorer.singleMetricChart.metricActualPlotFunctionLabel',
{
defaultMessage: 'function',
}
),
value: marker.metricActualPlotFunction,
seriesIdentifier: {
key: seriesKey,
},
valueAccessor: 'metricActualPlotFunction',
});
}

tooltipService.show(tooltipData, circle, {
x: LINE_CHART_ANOMALY_RADIUS * 3,
y: LINE_CHART_ANOMALY_RADIUS * 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,9 @@ export class TimeSeriesExplorer extends React.Component {
);
};

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

Expand Down Expand Up @@ -858,6 +860,36 @@ export class TimeSeriesExplorer extends React.Component {
if (detectorId !== selectedDetectorIndex) {
appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId);
}
// if the detector's function is metric, fetch the highest scoring anomaly record
// and set to plot the function_description (avg/min/max) of that record by default
if (selectedJob?.analysis_config?.detectors[detectorIndex]?.function === 'metric') {
const entityControls = this.getControlsForDetector();

mlResultsService
.getRecordsForCriteria(
[selectedJob.job_id],
this.getCriteriaFields(selectedDetectorIndex, entityControls),
0,
null,
null,
1
)
.toPromise()
.then((resp) => {
if (Array.isArray(resp?.records) && resp.records.length === 1) {
const highestScoringAnomaly = resp.records[0];
this.setState({ actualPlotFunction: highestScoringAnomaly.function_description });
}
})
.catch((resp) => {
console.log(
'Time series explorer - error getting record with highest anomaly score:',
resp
);
});
} else {
this.setState({ actualPlotFunction: undefined });
}

// Populate the map of jobs / detectors / field formatters for the selected IDs and refresh.
mlFieldFormatService.populateFormats([jobId]).catch((err) => {
Expand Down Expand Up @@ -1221,6 +1253,42 @@ export class TimeSeriesExplorer extends React.Component {
/>
);
})}
{actualPlotFunction && (
<EuiFlexItem style={{ textAlign: 'right' }}>
<EuiFormRow
label={i18n.translate('xpack.ml.timeSeriesExplorer.metricPlotByOption', {
defaultMessage: 'Function',
})}
>
<EuiSelect
options={[
{
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',
}),
},
]}
value={actualPlotFunction ?? 'avg'}
onChange={(e) => this.setPlotByFunction(e.target.value)}
aria-label="Pick function to plot by (min, max, or average) if metric function"
/>
</EuiFormRow>
</EuiFlexItem>
)}

{arePartitioningFieldsProvided && (
<EuiFlexItem style={{ textAlign: 'right' }}>
<EuiFormRow hasEmptyLabelSpace style={{ maxWidth: '100%' }}>
Expand Down Expand Up @@ -1315,34 +1383,6 @@ export class TimeSeriesExplorer extends React.Component {
</h2>
</EuiTitle>

{actualPlotFunction && (
<EuiSelect
options={[
{
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',
}),
},
]}
value={actualPlotFunction ?? 'avg'}
onChange={(e) => this.setPlotByFunction(e.target.value)}
aria-label="Pick function to plot by (min, max, or average) if metric function"
/>
)}

<EuiFlexGroup style={{ float: 'right' }}>
{showModelBoundsCheckbox && (
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ export function getFocusData(
focusChartData,
anomalyRecords,
focusAggregationInterval,
modelPlotEnabled
modelPlotEnabled,
actualPlotFunction
);
focusChartData = processScheduledEventsForChart(focusChartData, scheduledEvents);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export function processDataForFocusAnomalies(
chartData: any,
anomalyRecords: any,
aggregationInterval: any,
modelPlotEnabled: any
modelPlotEnabled: any,
actualPlotFunction: any
): any;

export function processScheduledEventsForChart(chartData: any, scheduledEvents: any): any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ export function processDataForFocusAnomalies(
chartData,
anomalyRecords,
aggregationInterval,
modelPlotEnabled
modelPlotEnabled,
actualPlotFunction
) {
const timesToAddPointsFor = [];

Expand Down Expand Up @@ -142,6 +143,8 @@ export function processDataForFocusAnomalies(
// Look for a chart point with the same time as the record.
// If none found, find closest time in chartData set.
const recordTime = record[TIME_FIELD_NAME];
if (record.function === 'metric' && record.function_description !== actualPlotFunction) return;

const chartPoint = findChartPointForAnomalyTime(chartData, recordTime, aggregationInterval);
if (chartPoint !== undefined) {
// If chart aggregation interval > bucket span, there may be more than
Expand Down Expand Up @@ -183,6 +186,9 @@ export function processDataForFocusAnomalies(
if (record.multi_bucket_impact !== undefined) {
chartPoint.multiBucketImpact = record.multi_bucket_impact;
}
if (record.function === 'metric') {
chartPoint.metricActualPlotFunction = record.function_description ?? actualPlotFunction;
}
}
}
});
Expand Down

0 comments on commit ffce815

Please sign in to comment.