Skip to content

Commit

Permalink
Refactor AnomalyHistory Chart to improve performance for HC detector (o…
Browse files Browse the repository at this point in the history
  • Loading branch information
yizheliu-amazon committed Dec 31, 2020
1 parent 58d7c0f commit 1196e85
Show file tree
Hide file tree
Showing 16 changed files with 2,087 additions and 217 deletions.
31 changes: 28 additions & 3 deletions public/pages/AnomalyCharts/containers/AnomaliesChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { get } from 'lodash';
import moment, { DurationInputArg2 } from 'moment';
import React, { useState } from 'react';
import { EntityAnomalySummaries } from '../../../../server/models/interfaces';
import ContentPanel from '../../../components/ContentPanel/ContentPanel';
import { useDelayedLoader } from '../../../hooks/useDelayedLoader';
import {
Expand All @@ -38,6 +39,7 @@ import { AnomalyDetailsChart } from '../containers/AnomalyDetailsChart';
import {
AnomalyHeatmapChart,
HeatmapCell,
HeatmapDisplayOption,
} from '../containers/AnomalyHeatmapChart';
import {
getAnomalyGradeWording,
Expand Down Expand Up @@ -71,10 +73,13 @@ interface AnomaliesChartProps {
isHCDetector?: boolean;
detectorCategoryField?: string[];
onHeatmapCellSelected?(heatmapCell: HeatmapCell): void;
onDisplayOptionChanged?(heatmapDisplayOption: HeatmapDisplayOption): void;
selectedHeatmapCell?: HeatmapCell;
newDetector?: Detector;
zoomRange?: DateRange;
anomaliesResult: Anomalies | undefined;
heatmapDisplayOption?: HeatmapDisplayOption;
entityAnomalySummaries?: EntityAnomalySummaries[];
}

export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
Expand Down Expand Up @@ -172,6 +177,21 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
);
};

const hasValidHCProps = () => {
return (
props.isHCDetector &&
props.onHeatmapCellSelected &&
props.detectorCategoryField &&
// For Non-Sample HC detector case, aka realtime HC detector(showAlert == true),
// we use anomaly summaries data to render heatmap
// we must have function onDisplayOptionChanged and entityAnomalySummaries defined
// so that heatmap can work as expected.
(props.showAlerts !== true ||
(props.showAlerts &&
props.onDisplayOptionChanged &&
props.entityAnomalySummaries))
);
};
return (
<React.Fragment>
<ContentPanel
Expand All @@ -181,9 +201,7 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
}
>
<EuiFlexGroup direction="column">
{props.isHCDetector &&
props.onHeatmapCellSelected &&
props.detectorCategoryField ? (
{hasValidHCProps() ? (
<EuiFlexGroup style={{ padding: '20px' }}>
<EuiFlexItem style={{ margin: '0px' }}>
<div
Expand Down Expand Up @@ -221,7 +239,14 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
props.detector,
'detectionInterval.period.unit'
)}
//@ts-ignore
onHeatmapCellSelected={props.onHeatmapCellSelected}
entityAnomalySummaries={props.entityAnomalySummaries}
onDisplayOptionChanged={props.onDisplayOptionChanged}
heatmapDisplayOption={props.heatmapDisplayOption}
// TODO use props.isNotSample after Tyler's change is merged
// https://github.com/opendistro-for-elasticsearch/anomaly-detection-kibana-plugin/pull/350#discussion_r547009140
isNotSample={props.showAlerts === true}
/>,
props.showAlerts !== true
? [
Expand Down
174 changes: 124 additions & 50 deletions public/pages/AnomalyCharts/containers/AnomalyHeatmapChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import React, { useState } from 'react';

import moment from 'moment';
import { PlotData } from 'plotly.js';
import Plot from 'react-plotly.js';
import { get, isEmpty } from 'lodash';
import Plotly, { PlotData } from 'plotly.js-dist';
import plotComponentFactory from 'react-plotly.js/factory';
import { get, isEmpty, uniq } from 'lodash';
import {
EuiFlexItem,
EuiFlexGroup,
Expand All @@ -41,28 +41,52 @@ import {
AnomalyHeatmapSortType,
sortHeatmapPlotData,
filterHeatmapPlotDataByY,
getEntitytAnomaliesHeatmapData,
} from '../utils/anomalyChartUtils';
import { MIN_IN_MILLI_SECS } from '../../../../server/utils/constants';
import { EntityAnomalySummaries } from '../../../../server/models/interfaces';

interface AnomalyHeatmapChartProps {
title: string;
detectorId: string;
detectorName: string;
anomalies: any[];
anomalies?: any[];
dateRange: DateRange;
isLoading: boolean;
showAlerts?: boolean;
monitor?: Monitor;
detectorInterval?: number;
unit?: string;
onHeatmapCellSelected(cell: HeatmapCell | undefined): void;
onDisplayOptionChanged?(option: HeatmapDisplayOption | undefined): void;
heatmapDisplayOption?: HeatmapDisplayOption;
entityAnomalySummaries?: EntityAnomalySummaries[];
isNotSample?: boolean;
}

export interface HeatmapCell {
dateRange: DateRange;
entityValue: string;
}

export interface HeatmapDisplayOption {
sortType: AnomalyHeatmapSortType;
entityOption: { label: string; value: number };
}

const COMBINED_OPTIONS = {
label: 'Combined options',
options: [
{ label: 'Top 10', value: 10 },
{ label: 'Top 20', value: 20 },
{ label: 'Top 30', value: 30 },
],
};

export const INITIAL_HEATMAP_DISPLAY_OPTION = {
sortType: AnomalyHeatmapSortType.SEVERITY,
entityOption: COMBINED_OPTIONS.options[0],
} as HeatmapDisplayOption;

export const AnomalyHeatmapChart = React.memo(
(props: AnomalyHeatmapChartProps) => {
const showLoader = useDelayedLoader(props.isLoading);
Expand All @@ -80,14 +104,7 @@ export const AnomalyHeatmapChart = React.memo(
},
];

const COMBINED_OPTIONS = {
label: 'Combined options',
options: [
{ label: 'Top 10', value: 10 },
{ label: 'Top 20', value: 20 },
{ label: 'Top 30', value: 30 },
],
};
const PlotComponent = plotComponentFactory(Plotly);

const getViewEntityOptions = (inputHeatmapData: PlotData[]) => {
let individualEntities = [];
Expand All @@ -107,28 +124,57 @@ export const AnomalyHeatmapChart = React.memo(
});

return [
COMBINED_OPTIONS,
getViewableCombinedOptions(
COMBINED_OPTIONS,
props.heatmapDisplayOption?.entityOption
),
{
label: 'Individual entities',
options: individualEntityOptions,
options: individualEntityOptions.reverse(),
},
];
};

const getViewableCombinedOptions = (
existingOptions: any,
selectedCombinedOption: any | undefined
) => {
if (!selectedCombinedOption) {
return existingOptions;
}
return {
label: existingOptions.label,
options: uniq([selectedCombinedOption, ...existingOptions.options]),
};
};

const [originalHeatmapData, setOriginalHeatmapData] = useState(
getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
AnomalyHeatmapSortType.SEVERITY,
COMBINED_OPTIONS.options[0].value
)
props.isNotSample
? // use anomaly summary data in case of realtime result
getEntitytAnomaliesHeatmapData(
props.dateRange,
props.entityAnomalySummaries,
props.heatmapDisplayOption.entityOption.value
)
: // use anomalies data in case of sample result
getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
AnomalyHeatmapSortType.SEVERITY,
COMBINED_OPTIONS.options[0].value
)
);

const [heatmapData, setHeatmapData] = useState<PlotData[]>(
originalHeatmapData
);

const [sortByFieldValue, setSortByFieldValue] = useState(
SORT_BY_FIELD_OPTIONS[0].value
const [sortByFieldValue, setSortByFieldValue] = useState<
AnomalyHeatmapSortType
>(
props.isNotSample
? props.heatmapDisplayOption.sortType
: SORT_BY_FIELD_OPTIONS[0].value
);

const [currentViewOptions, setCurrentViewOptions] = useState([
Expand Down Expand Up @@ -191,14 +237,14 @@ export const AnomalyHeatmapChart = React.memo(
);
setHeatmapData([transparentHeatmapData, ...selectedHeatmapData]);

const selectedEndDate = moment(
const selectedStartDate = moment(
//@ts-ignore
heatmapData[0].x[selectedCellIndices[1]],
HEATMAP_X_AXIS_DATE_FORMAT
).valueOf();

const selectedStartDate =
selectedEndDate -
const selectedEndDate =
selectedStartDate +
get(selectedHeatmapData, '[0].cellTimeInterval', MIN_IN_MILLI_SECS);
props.onHeatmapCellSelected({
dateRange: {
Expand Down Expand Up @@ -226,18 +272,25 @@ export const AnomalyHeatmapChart = React.memo(
if (isEmpty(selectedViewOptions)) {
// when `clear` is hit for combo box
setCurrentViewOptions([COMBINED_OPTIONS.options[0]]);
const displayTopEntityNum = get(COMBINED_OPTIONS.options[0], 'value');

const updateHeatmapPlotData = getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
sortByFieldValue,
displayTopEntityNum
);
setOriginalHeatmapData(updateHeatmapPlotData);
setHeatmapData(updateHeatmapPlotData);
setNumEntities(updateHeatmapPlotData[0].y.length);
setEntityViewOptions(getViewEntityOptions(updateHeatmapPlotData));
if (props.isNotSample && props.onDisplayOptionChanged) {
props.onDisplayOptionChanged({
sortType: sortByFieldValue,
entityOption: COMBINED_OPTIONS.options[0],
});
} else {
const displayTopEntityNum = get(COMBINED_OPTIONS.options[0], 'value');
const updateHeatmapPlotData = getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
sortByFieldValue,
displayTopEntityNum
);
setOriginalHeatmapData(updateHeatmapPlotData);
setHeatmapData(updateHeatmapPlotData);
setNumEntities(updateHeatmapPlotData[0].y.length);
setEntityViewOptions(getViewEntityOptions(updateHeatmapPlotData));
}
return;
}
const nonCombinedOptions = [] as any[];
Expand All @@ -251,17 +304,26 @@ export const AnomalyHeatmapChart = React.memo(
if (isCombinedViewEntityOption(option)) {
// only allow 1 combined option
setCurrentViewOptions([option]);
const displayTopEntityNum = get(option, 'value');
const updateHeatmapPlotData = getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
sortByFieldValue,
displayTopEntityNum
);
setOriginalHeatmapData(updateHeatmapPlotData);
setHeatmapData(updateHeatmapPlotData);
setNumEntities(updateHeatmapPlotData[0].y.length);
setEntityViewOptions(getViewEntityOptions(updateHeatmapPlotData));
if (props.isNotSample && props.onDisplayOptionChanged) {
props.onDisplayOptionChanged({
sortType: sortByFieldValue,
entityOption: option,
});
} else {
const displayTopEntityNum = get(option, 'value');
const updateHeatmapPlotData = getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
sortByFieldValue,
displayTopEntityNum
);

setOriginalHeatmapData(updateHeatmapPlotData);
setHeatmapData(updateHeatmapPlotData);
setNumEntities(updateHeatmapPlotData[0].y.length);
setEntityViewOptions(getViewEntityOptions(updateHeatmapPlotData));
}

return;
} else {
nonCombinedOptions.push(option);
Expand Down Expand Up @@ -294,6 +356,19 @@ export const AnomalyHeatmapChart = React.memo(

const handleSortByFieldChange = (value: any) => {
setSortByFieldValue(value);
props.onHeatmapCellSelected(undefined);
if (
props.isNotSample &&
props.onDisplayOptionChanged &&
currentViewOptions.length === 1 &&
isCombinedViewEntityOption(currentViewOptions[0])
) {
props.onDisplayOptionChanged({
sortType: value,
entityOption: currentViewOptions[0],
});
return;
}
const sortedHeatmapData = sortHeatmapPlotData(
heatmapData[0],
value,
Expand All @@ -303,7 +378,6 @@ export const AnomalyHeatmapChart = React.memo(
opacity: 1,
});
setHeatmapData([updatedHeatmapData]);
props.onHeatmapCellSelected(undefined);
};

return (
Expand Down Expand Up @@ -439,7 +513,7 @@ export const AnomalyHeatmapChart = React.memo(
</EuiFlexItem>
</EuiFlexGroup>
) : (
<Plot
<PlotComponent
data={heatmapData}
style={{
position: 'relative',
Expand Down
Loading

0 comments on commit 1196e85

Please sign in to comment.