[ML] Fix Anomaly Explorer continuously re-render on swim lane cell selection (#98196)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Quynh Nguyen 2021-04-26 14:08:26 -05:00 committed by GitHub
parent 56bce08611
commit fd66e075ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 50 additions and 33 deletions

View file

@ -37,7 +37,6 @@ import { TimefilterContract } from '../../../../../../../src/plugins/data/public
import { AnomalyExplorerChartsService } from '../../services/anomaly_explorer_charts_service';
import { CombinedJob } from '../../../../common/types/anomaly_detection_jobs';
import { InfluencersFilterQuery } from '../../../../common/types/es_client';
import { ExplorerChartsData } from '../explorer_charts/explorer_charts_container_service';
import { mlJobService } from '../../services/job_service';
import { TimeBucketsInterval } from '../../util/time_buckets';
@ -156,7 +155,6 @@ const loadExplorerDataProvider = (
const dateFormatTz = getDateFormatTz();
const interval = swimlaneBucketInterval.asSeconds();
// First get the data where we have all necessary args at hand using forkJoin:
// annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues
return forkJoin({
@ -225,7 +223,21 @@ const loadExplorerDataProvider = (
// show the view-by loading indicator
// and pass on the data we already fetched.
tap(explorerService.setViewBySwimlaneLoading),
tap(explorerService.setChartsDataLoading),
tap(({ anomalyChartRecords, topFieldValues }) => {
memoizedAnomalyDataChange(
lastRefresh,
explorerService,
combinedJobRecords,
swimlaneContainerWidth,
selectedCells !== undefined && Array.isArray(anomalyChartRecords)
? anomalyChartRecords
: [],
timerange.earliestMs,
timerange.latestMs,
timefilter,
tableSeverity
);
}),
mergeMap(
({
overallAnnotations,
@ -237,18 +249,6 @@ const loadExplorerDataProvider = (
tableData,
}) =>
forkJoin({
anomalyChartsData: memoizedAnomalyDataChange(
lastRefresh,
combinedJobRecords,
swimlaneContainerWidth,
selectedCells !== undefined && Array.isArray(anomalyChartRecords)
? anomalyChartRecords
: [],
timerange.earliestMs,
timerange.latestMs,
timefilter,
tableSeverity
),
filteredTopInfluencers:
(selectionInfluencers.length > 0 || influencersFilterQuery !== undefined) &&
anomalyChartRecords !== undefined &&
@ -281,9 +281,6 @@ const loadExplorerDataProvider = (
influencersFilterQuery
),
}).pipe(
tap(({ anomalyChartsData }) => {
explorerService.setCharts(anomalyChartsData as ExplorerChartsData);
}),
map(({ viewBySwimlaneState, filteredTopInfluencers }) => {
return {
overallAnnotations,

View file

@ -66,7 +66,7 @@ export const AnomalyContextMenu: FC<AnomalyContextMenuProps> = ({
return (
<>
{menuItems.length > 0 && (
{menuItems.length > 0 && chartsCount > 0 && (
<EuiFlexItem grow={false} style={{ marginLeft: 'auto', alignSelf: 'baseline' }}>
<EuiPopover
button={

View file

@ -90,6 +90,8 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
overallAnnotations,
} = explorerState;
const annotations = useMemo(() => overallAnnotations.annotationsData, [overallAnnotations]);
const menuItems = useMemo(() => {
const items = [];
if (canEditDashboards) {
@ -241,7 +243,7 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
isLoading={loading}
noDataWarning={<NoOverallData />}
showTimeline={false}
annotationsData={overallAnnotations.annotationsData}
annotationsData={annotations}
/>
<EuiSpacer size="m" />

View file

@ -143,7 +143,7 @@ export const SwimlaneAnnotationContainer: FC<SwimlaneAnnotationContainerProps> =
.on('mouseout', () => tooltipService.hide());
});
}
}, [chartWidth, domain, annotationsData]);
}, [chartWidth, domain, annotationsData, tooltipService]);
return <div ref={canvasRef} />;
};

View file

@ -369,13 +369,17 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
[swimlaneData?.fieldName]
);
const xDomain = swimlaneData
? {
min: swimlaneData.earliest * 1000,
max: swimlaneData.latest * 1000,
minInterval: swimlaneData.interval * 1000,
}
: undefined;
const xDomain = useMemo(
() =>
swimlaneData
? {
min: swimlaneData.earliest * 1000,
max: swimlaneData.latest * 1000,
minInterval: swimlaneData.interval * 1000,
}
: undefined,
[swimlaneData]
);
// A resize observer is required to compute the bucket span based on the chart width to fetch the data accordingly
return (
@ -392,6 +396,7 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
style={{
width: '100%',
overflowY: 'auto',
overflowX: 'hidden',
}}
grow={false}
>
@ -403,11 +408,7 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
onElementClick={onElementClick}
showLegend={showLegend}
legendPosition={Position.Top}
xDomain={{
min: swimlaneData.earliest * 1000,
max: swimlaneData.latest * 1000,
minInterval: swimlaneData.interval * 1000,
}}
xDomain={xDomain}
tooltip={tooltipOptions}
debugState={window._echDebugStateFlag ?? false}
/>

View file

@ -102,6 +102,7 @@ describe('AnomalyExplorerChartsService', () => {
test('should return anomaly data without explorer service', async () => {
const anomalyData = (await anomalyExplorerService.getAnomalyData(
undefined,
(combinedJobRecords as unknown) as Record<string, CombinedJob>,
1000,
mockAnomalyChartRecords,
@ -116,6 +117,7 @@ describe('AnomalyExplorerChartsService', () => {
test('call anomalyChangeListener with empty series config', async () => {
const anomalyData = (await anomalyExplorerService.getAnomalyData(
undefined,
// @ts-ignore
(combinedJobRecords as unknown) as Record<string, CombinedJob>,
1000,
@ -137,6 +139,7 @@ describe('AnomalyExplorerChartsService', () => {
mockAnomalyChartRecordsClone[1].partition_field_value = 'AAL.';
const anomalyData = (await anomalyExplorerService.getAnomalyData(
undefined,
(combinedJobRecords as unknown) as Record<string, CombinedJob>,
1000,
mockAnomalyChartRecordsClone,

View file

@ -40,6 +40,7 @@ import { TimeRangeBounds } from '../util/time_buckets';
import { isDefined } from '../../../common/types/guards';
import { AppStateSelectedCells } from '../explorer/explorer_utils';
import { InfluencersFilterQuery } from '../../../common/types/es_client';
import { ExplorerService } from '../explorer/explorer_dashboard_service';
const CHART_MAX_POINTS = 500;
const ANOMALIES_MAX_RESULTS = 500;
const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket.
@ -427,6 +428,7 @@ export class AnomalyExplorerChartsService {
}
public async getAnomalyData(
explorerService: ExplorerService | undefined,
combinedJobRecords: Record<string, CombinedJob>,
chartsContainerWidth: number,
anomalyRecords: ChartRecord[] | undefined,
@ -534,6 +536,11 @@ export class AnomalyExplorerChartsService {
data.errorMessages = errorMessages;
}
// TODO: replace this temporary fix for flickering issue
// https://github.com/elastic/kibana/issues/97266
if (explorerService) {
explorerService.setCharts({ ...data });
}
if (seriesConfigs.length === 0) {
return data;
}
@ -895,6 +902,12 @@ export class AnomalyExplorerChartsService {
// push map data in if it's available
data.seriesToPlot.push(...mapData);
}
// TODO: replace this temporary fix for flickering issue
if (explorerService) {
explorerService.setCharts({ ...data });
}
return Promise.resolve(data);
})
.catch((error) => {

View file

@ -133,6 +133,7 @@ export function useAnomalyChartsInputResolver(
return forkJoin({
chartsData: from(
anomalyExplorerService.getAnomalyData(
undefined,
combinedJobRecords,
embeddableContainerWidth,
anomalyChartRecords,