Skip to content

Commit

Permalink
[ML] Fixing wizard charts repeated loading (#44409) (#44624)
Browse files Browse the repository at this point in the history
* [ML] Caching wizard chart data

* using shared chart settings hook

* fixing settings hook and switching to memoize-one

* removing custom hook
  • Loading branch information
jgowdyelastic committed Sep 2, 2019
1 parent fd311c2 commit 65b4c2b
Show file tree
Hide file tree
Showing 17 changed files with 609 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { useKibanaContext, SavedSearchQuery } from '../../contexts/kibana';
import { kbnTypeToMLJobType } from '../../util/field_types_utils';
// @ts-ignore
import { timeBasedIndexCheck } from '../../util/index_utils';
// @ts-ignore
import { MlTimeBuckets } from '../../util/ml_time_buckets';
import { FieldRequestConfig, FieldVisConfig } from './common';
import { ActionsPanel } from './components/actions_panel';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types';
import memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';
import { IndexPattern } from 'ui/index_patterns';
import { IndexPatternTitle } from '../../../../../common/types/kibana';
import { Field, SplitField, AggFieldPair } from '../../../../../common/types/fields';
import { ml } from '../../../../services/ml_api_service';
import { mlResultsService } from '../../../../services/results_service';
import { getCategoryFields } from './searches';
import { getCategoryFields as getCategoryFieldsOrig } from './searches';

type DetectorIndex = number;
export interface LineChartPoint {
Expand All @@ -20,16 +21,19 @@ export interface LineChartPoint {
type SplitFieldValue = string | null;
export type LineChartData = Record<DetectorIndex, LineChartPoint[]>;

const eq = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs);

const newJobLineChart = memoizeOne(ml.jobs.newJobLineChart, eq);
const newJobPopulationsChart = memoizeOne(ml.jobs.newJobPopulationsChart, eq);
const getEventRateData = memoizeOne(mlResultsService.getEventRateData, eq);
const getCategoryFields = memoizeOne(getCategoryFieldsOrig, eq);

export class ChartLoader {
protected _indexPattern: IndexPattern;
protected _savedSearch: SavedSearch;
protected _indexPatternTitle: IndexPatternTitle = '';
protected _timeFieldName: string = '';
protected _query: object = {};

constructor(indexPattern: IndexPattern, savedSearch: SavedSearch, query: object) {
this._indexPattern = indexPattern;
this._savedSearch = savedSearch;
private _indexPatternTitle: IndexPatternTitle = '';
private _timeFieldName: string = '';
private _query: object = {};

constructor(indexPattern: IndexPattern, query: object) {
this._indexPatternTitle = indexPattern.title;
this._query = query;

Expand All @@ -49,7 +53,7 @@ export class ChartLoader {
if (this._timeFieldName !== '') {
const splitFieldName = splitField !== null ? splitField.name : null;

const resp = await ml.jobs.newJobLineChart(
const resp = await newJobLineChart(
this._indexPatternTitle,
this._timeFieldName,
start,
Expand All @@ -60,6 +64,9 @@ export class ChartLoader {
splitFieldName,
splitFieldValue
);
if (resp.error !== undefined) {
throw resp.error;
}
return resp.results;
}
return {};
Expand All @@ -75,7 +82,7 @@ export class ChartLoader {
if (this._timeFieldName !== '') {
const splitFieldName = splitField !== null ? splitField.name : '';

const resp = await ml.jobs.newJobPopulationsChart(
const resp = await newJobPopulationsChart(
this._indexPatternTitle,
this._timeFieldName,
start,
Expand All @@ -85,6 +92,9 @@ export class ChartLoader {
aggFieldPairs.map(getAggFieldPairNames),
splitFieldName
);
if (resp.error !== undefined) {
throw resp.error;
}
return resp.results;
}
return {};
Expand All @@ -96,14 +106,18 @@ export class ChartLoader {
intervalMs: number
): Promise<LineChartPoint[]> {
if (this._timeFieldName !== '') {
const resp = await mlResultsService.getEventRateData(
const resp = await getEventRateData(
this._indexPatternTitle,
this._query,
this._timeFieldName,
start,
end,
intervalMs * 3
);
if (resp.error !== undefined) {
throw resp.error;
}

return Object.entries(resp.results).map(([time, value]) => ({
time: +time,
value: value as number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
import chrome from 'ui/chrome';
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import {
SingleMetricJobCreator,
MultiMetricJobCreator,
PopulationJobCreator,
isMultiMetricJobCreator,
isPopulationJobCreator,
} from '../../../../common/job_creator';
import { MlTimeBuckets } from '../../../../../../util/ml_time_buckets';

const IS_DARK_THEME = chrome.getUiSettingsClient().get('theme:darkMode');
const themeName = IS_DARK_THEME ? darkTheme : lightTheme;
Expand Down Expand Up @@ -50,3 +58,38 @@ export const seriesStyle = {
visible: false,
},
};

export function getChartSettings(
jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator,
chartInterval: MlTimeBuckets
) {
const cs = {
...defaultChartSettings,
intervalMs: chartInterval.getInterval().asMilliseconds(),
};

if (isPopulationJobCreator(jobCreator)) {
// for population charts, use a larger interval based on
// the calculation from MlTimeBuckets, but without the
// bar target and max bars which have been set for the
// general chartInterval
const interval = new MlTimeBuckets();
interval.setInterval('auto');
interval.setBounds(chartInterval.getBounds());
cs.intervalMs = interval.getInterval().asMilliseconds();
}

if (isMultiMetricJobCreator(jobCreator) || isPopulationJobCreator(jobCreator)) {
if (jobCreator.aggFieldPairs.length > 2 && isMultiMetricJobCreator(jobCreator)) {
cs.cols = 3;
cs.height = '150px';
cs.intervalMs = cs.intervalMs * 3;
} else if (jobCreator.aggFieldPairs.length > 1) {
cs.cols = 2;
cs.height = '200px';
cs.intervalMs = cs.intervalMs * 2;
}
}

return cs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,26 @@ import React, { Fragment, FC, useContext, useEffect, useState } from 'react';

import { JobCreatorContext } from '../../../job_creator_context';
import { MultiMetricJobCreator, isMultiMetricJobCreator } from '../../../../../common/job_creator';
import { Results, ModelItem, Anomaly } from '../../../../../common/results_loader';
import { LineChartData } from '../../../../../common/chart_loader';
import { DropDownLabel, DropDownProps } from '../agg_select';
import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service';
import { AggFieldPair } from '../../../../../../../../common/types/fields';
import { defaultChartSettings, ChartSettings } from '../../../charts/common/settings';
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
import { MetricSelector } from './metric_selector';
import { ChartGrid } from './chart_grid';
import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service';

interface Props {
isActive: boolean;
setIsValid: (na: boolean) => void;
}

export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => {
const {
jobCreator: jc,
jobCreatorUpdate,
jobCreatorUpdated,
chartLoader,
chartInterval,
resultsLoader,
} = useContext(JobCreatorContext);

if (isMultiMetricJobCreator(jc) === false) {
Expand All @@ -45,14 +42,12 @@ export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
);
const [lineChartsData, setLineChartsData] = useState<LineChartData>({});
const [loadingData, setLoadingData] = useState(false);
const [modelData, setModelData] = useState<Record<number, ModelItem[]>>([]);
const [anomalyData, setAnomalyData] = useState<Record<number, Anomaly[]>>([]);
const [start, setStart] = useState(jobCreator.start);
const [end, setEnd] = useState(jobCreator.end);

const [chartSettings, setChartSettings] = useState(defaultChartSettings);
const [splitField, setSplitField] = useState(jobCreator.splitField);
const [fieldValues, setFieldValues] = useState<string[]>([]);
const [pageReady, setPageReady] = useState(false);

function detectorChangeHandler(selectedOptionsIn: DropDownLabel[]) {
addDetector(selectedOptionsIn);
Expand All @@ -76,17 +71,8 @@ export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
setAggFieldPairList([...aggFieldPairList]);
}

function setResultsWrapper(results: Results) {
setModelData(results.model);
setAnomalyData(results.anomalies);
}

useEffect(() => {
// subscribe to progress and results
const subscription = resultsLoader.subscribeToResults(setResultsWrapper);
return () => {
subscription.unsubscribe();
};
setPageReady(true);
}, []);

// watch for changes in detector list length
Expand Down Expand Up @@ -134,30 +120,12 @@ export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
loadCharts();
}, [fieldValues]);

function getChartSettings(): ChartSettings {
const cs = {
...defaultChartSettings,
intervalMs: chartInterval.getInterval().asMilliseconds(),
};
if (aggFieldPairList.length > 2) {
cs.cols = 3;
cs.height = '150px';
cs.intervalMs = cs.intervalMs * 3;
} else if (aggFieldPairList.length > 1) {
cs.cols = 2;
cs.height = '200px';
cs.intervalMs = cs.intervalMs * 2;
}
return cs;
}

async function loadCharts() {
const cs = getChartSettings();
setChartSettings(cs);

if (aggFieldPairList.length > 0) {
if (allDataReady()) {
setLoadingData(true);
try {
const cs = getChartSettings(jobCreator, chartInterval);
setChartSettings(cs);
const resp: LineChartData = await chartLoader.loadLineCharts(
jobCreator.start,
jobCreator.end,
Expand All @@ -175,6 +143,14 @@ export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
}
}

function allDataReady() {
return (
pageReady &&
aggFieldPairList.length > 0 &&
(splitField === null || (splitField !== null && fieldValues.length > 0))
);
}

return (
<Fragment>
<ChartGrid
Expand All @@ -183,21 +159,19 @@ export const MultiMetricDetectors: FC<Props> = ({ isActive, setIsValid }) => {
splitField={splitField}
fieldValues={fieldValues}
lineChartsData={lineChartsData}
modelData={modelData}
anomalyData={anomalyData}
deleteDetector={isActive ? deleteDetector : undefined}
modelData={[]}
anomalyData={[]}
deleteDetector={deleteDetector}
jobType={jobCreator.type}
loading={loadingData}
/>

{isActive && (
<MetricSelector
fields={fields}
detectorChangeHandler={detectorChangeHandler}
selectedOptions={selectedOptions}
removeOptions={aggFieldPairList}
/>
)}
<MetricSelector
fields={fields}
detectorChangeHandler={detectorChangeHandler}
selectedOptions={selectedOptions}
removeOptions={aggFieldPairList}
/>
</Fragment>
);
};
Loading

0 comments on commit 65b4c2b

Please sign in to comment.