Skip to content

Commit

Permalink
[ML] Persist URL state for Anomaly detection jobs using metric functi…
Browse files Browse the repository at this point in the history
…on (#83507)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
qn895 and kibanamachine authored Nov 18, 2020
1 parent 7a7057e commit 77da781
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 75 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/common/types/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export interface TimeSeriesExplorerAppState {
forecastId?: string;
detectorIndex?: number;
entities?: Record<string, string>;
functionDescription?: string;
};
query?: any;
}
Expand All @@ -145,6 +146,7 @@ export interface TimeSeriesExplorerPageState
entities?: Record<string, string>;
forecastId?: string;
globalState?: MlCommonGlobalState;
functionDescription?: string;
}

export type TimeSeriesExplorerUrlState = MLPageState<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ export const TimeSeriesExplorerUrlStateManager: FC<TimeSeriesExplorerUrlStateMan
: +appState?.mlTimeSeriesExplorer?.detectorIndex || 0;
const selectedEntities = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.entities;
const selectedForecastId = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.forecastId;

const selectedFunctionDescription = isJobChange
? undefined
: appState?.mlTimeSeriesExplorer?.functionDescription;

const zoom: AppStateZoom | undefined = isJobChange
? undefined
: appState?.mlTimeSeriesExplorer?.zoom;
Expand All @@ -184,14 +189,19 @@ export const TimeSeriesExplorerUrlStateManager: FC<TimeSeriesExplorerUrlStateMan
delete mlTimeSeriesExplorer.entities;
delete mlTimeSeriesExplorer.forecastId;
delete mlTimeSeriesExplorer.zoom;
delete mlTimeSeriesExplorer.functionDescription;
break;

case APP_STATE_ACTION.SET_DETECTOR_INDEX:
mlTimeSeriesExplorer.detectorIndex = payload;
delete mlTimeSeriesExplorer.functionDescription;

break;

case APP_STATE_ACTION.SET_ENTITIES:
mlTimeSeriesExplorer.entities = payload;
delete mlTimeSeriesExplorer.functionDescription;

break;

case APP_STATE_ACTION.SET_FORECAST_ID:
Expand All @@ -206,6 +216,10 @@ export const TimeSeriesExplorerUrlStateManager: FC<TimeSeriesExplorerUrlStateMan
case APP_STATE_ACTION.UNSET_ZOOM:
delete mlTimeSeriesExplorer.zoom;
break;

case APP_STATE_ACTION.SET_FUNCTION_DESCRIPTION:
mlTimeSeriesExplorer.functionDescription = payload;
break;
}

setAppState('mlTimeSeriesExplorer', mlTimeSeriesExplorer);
Expand Down Expand Up @@ -315,6 +329,7 @@ export const TimeSeriesExplorerUrlStateManager: FC<TimeSeriesExplorerUrlStateMan
timefilter,
zoom: zoomProp,
invalidTimeRangeError,
functionDescription: selectedFunctionDescription,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
* 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 React, { useCallback, useEffect } from 'react';
import { EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { mlJobService } from '../../../services/job_service';
import { getFunctionDescription, isMetricDetector } from '../../get_function_description';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import { ML_JOB_AGGREGATION } from '../../../../../common/constants/aggregation_types';
import type { CombinedJob } from '../../../../../common/types/anomaly_detection_jobs';

const plotByFunctionOptions = [
{
Expand All @@ -30,11 +35,70 @@ const plotByFunctionOptions = [
export const PlotByFunctionControls = ({
functionDescription,
setFunctionDescription,
selectedDetectorIndex,
selectedJobId,
selectedEntities,
}: {
functionDescription: undefined | string;
setFunctionDescription: (func: string) => void;
selectedDetectorIndex: number;
selectedJobId: string;
selectedEntities: Record<string, any>;
}) => {
const toastNotificationService = useToastNotificationService();

const getFunctionDescriptionToPlot = useCallback(
async (
_selectedDetectorIndex: number,
_selectedEntities: Record<string, any>,
_selectedJobId: string,
_selectedJob: CombinedJob
) => {
const functionToPlot = await getFunctionDescription(
{
selectedDetectorIndex: _selectedDetectorIndex,
selectedEntities: _selectedEntities,
selectedJobId: _selectedJobId,
selectedJob: _selectedJob,
},
toastNotificationService
);
setFunctionDescription(functionToPlot);
},
[setFunctionDescription, toastNotificationService]
);

useEffect(() => {
if (functionDescription !== undefined) {
return;
}
const selectedJob = mlJobService.getJob(selectedJobId);
if (
// set if only entity controls are picked
selectedEntities !== undefined &&
functionDescription === undefined &&
isMetricDetector(selectedJob, selectedDetectorIndex)
) {
const detector = selectedJob.analysis_config.detectors[selectedDetectorIndex];
if (detector?.function === ML_JOB_AGGREGATION.METRIC) {
getFunctionDescriptionToPlot(
selectedDetectorIndex,
selectedEntities,
selectedJobId,
selectedJob
);
}
}
}, [
setFunctionDescription,
selectedDetectorIndex,
selectedEntities,
selectedJobId,
functionDescription,
]);

if (functionDescription === undefined) return null;

return (
<EuiFlexItem grow={false}>
<EuiFormRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import {
EntityControlProps,
} from '../entity_control/entity_control';
import { getControlsForDetector } from '../../get_controls_for_detector';
// @ts-ignore
import { getViewableDetectors } from '../../timeseriesexplorer';
import {
ML_ENTITY_FIELDS_CONFIG,
PartitionFieldConfig,
Expand All @@ -29,6 +27,7 @@ import {
import { useStorage } from '../../../contexts/ml/use_storage';
import { EntityFieldType } from '../../../../../common/types/anomalies';
import { FieldDefinition } from '../../../services/results_service/result_service_rx';
import { getViewableDetectors } from '../../timeseriesexplorer_utils/get_viewable_detectors';

function getEntityControlOptions(fieldValues: FieldDefinition['values']): ComboBoxOption[] {
if (!Array.isArray(fieldValues)) {
Expand Down Expand Up @@ -63,7 +62,7 @@ const getDefaultFieldConfig = (
};

interface SeriesControlsProps {
selectedDetectorIndex: any;
selectedDetectorIndex: number;
selectedJobId: JobId;
bounds: any;
appStateHandler: Function;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import { getControlsForDetector } from './get_controls_for_detector';
import { getCriteriaFields } from './get_criteria_fields';
import { CombinedJob } from '../../../common/types/anomaly_detection_jobs';
import { ML_JOB_AGGREGATION } from '../../../common/constants/aggregation_types';
import { getViewableDetectors } from './timeseriesexplorer_utils/get_viewable_detectors';

export function isMetricDetector(selectedJob: CombinedJob, selectedDetectorIndex: number) {
const detectors = getViewableDetectors(selectedJob);
if (Array.isArray(detectors) && detectors.length >= selectedDetectorIndex) {
const detector = selectedJob.analysis_config.detectors[selectedDetectorIndex];
if (detector?.function === ML_JOB_AGGREGATION.METRIC) {
return true;
}
}
return false;
}

/**
* Get the function description from the record with the highest anomaly score
Expand All @@ -31,18 +43,15 @@ export const getFunctionDescription = async (
) => {
// 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[selectedDetectorIndex]?.function !==
ML_JOB_AGGREGATION.METRIC
)
return;
if (!isMetricDetector(selectedJob, selectedDetectorIndex)) return;

const entityControls = getControlsForDetector(
selectedDetectorIndex,
selectedEntities,
selectedJobId
);
const criteriaFields = getCriteriaFields(selectedDetectorIndex, entityControls);

try {
const resp = await mlResultsService
.getRecordsForCriteria([selectedJob.job_id], criteriaFields, 0, null, null, 1)
Expand Down
Loading

0 comments on commit 77da781

Please sign in to comment.