mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Fix Anomaly Explorer data refresh with relative time bounds (#86142)
* [ML] use time bounds from the timeFilter * [ML] add manual type casting for times
This commit is contained in:
parent
b70cabbdcb
commit
bf60796cd3
10 changed files with 56 additions and 53 deletions
|
@ -90,7 +90,7 @@ export interface ExplorerAppState {
|
|||
mlExplorerSwimlane: {
|
||||
selectedType?: 'overall' | 'viewBy';
|
||||
selectedLanes?: string[];
|
||||
selectedTimes?: number[];
|
||||
selectedTimes?: [number, number];
|
||||
showTopFieldValues?: boolean;
|
||||
viewByFieldName?: string;
|
||||
viewByPerPage?: number;
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
loadTopInfluencers,
|
||||
AppStateSelectedCells,
|
||||
ExplorerJob,
|
||||
TimeRangeBounds,
|
||||
} from '../explorer_utils';
|
||||
import { ExplorerState } from '../reducers';
|
||||
import { useMlKibana, useTimefilter } from '../../contexts/kibana';
|
||||
|
@ -34,6 +33,7 @@ import { AnomalyTimelineService } from '../../services/anomaly_timeline_service'
|
|||
import { mlResultsServiceProvider } from '../../services/results_service';
|
||||
import { isViewBySwimLaneData } from '../swimlane_container';
|
||||
import { ANOMALY_SWIM_LANE_HARD_LIMIT } from '../explorer_constants';
|
||||
import { TimefilterContract } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
// Memoize the data fetching methods.
|
||||
// wrapWithLastRefreshArg() wraps any given function and preprends a `lastRefresh` argument
|
||||
|
@ -63,7 +63,6 @@ const memoizedLoadTopInfluencers = memoize(loadTopInfluencers);
|
|||
const memoizedLoadAnomaliesTableData = memoize(loadAnomaliesTableData);
|
||||
|
||||
export interface LoadExplorerDataConfig {
|
||||
bounds: TimeRangeBounds;
|
||||
influencersFilterQuery: any;
|
||||
lastRefresh: number;
|
||||
noInfluencersConfigured: boolean;
|
||||
|
@ -82,7 +81,6 @@ export interface LoadExplorerDataConfig {
|
|||
export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfig => {
|
||||
return (
|
||||
arg !== undefined &&
|
||||
arg.bounds !== undefined &&
|
||||
arg.selectedJobs !== undefined &&
|
||||
arg.selectedJobs !== null &&
|
||||
arg.viewBySwimlaneFieldName !== undefined
|
||||
|
@ -92,7 +90,10 @@ export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfi
|
|||
/**
|
||||
* Fetches the data necessary for the Anomaly Explorer using observables.
|
||||
*/
|
||||
const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService) => {
|
||||
const loadExplorerDataProvider = (
|
||||
anomalyTimelineService: AnomalyTimelineService,
|
||||
timefilter: TimefilterContract
|
||||
) => {
|
||||
const memoizedLoadOverallData = memoize(
|
||||
anomalyTimelineService.loadOverallData,
|
||||
anomalyTimelineService
|
||||
|
@ -107,7 +108,6 @@ const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService
|
|||
}
|
||||
|
||||
const {
|
||||
bounds,
|
||||
lastRefresh,
|
||||
influencersFilterQuery,
|
||||
noInfluencersConfigured,
|
||||
|
@ -125,6 +125,9 @@ const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService
|
|||
|
||||
const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName);
|
||||
const jobIds = getSelectionJobIds(selectedCells, selectedJobs);
|
||||
|
||||
const bounds = timefilter.getBounds();
|
||||
|
||||
const timerange = getSelectionTimeRange(
|
||||
selectedCells,
|
||||
swimlaneBucketInterval.asSeconds(),
|
||||
|
@ -287,12 +290,12 @@ export const useExplorerData = (): [Partial<ExplorerState> | undefined, (d: any)
|
|||
} = useMlKibana();
|
||||
|
||||
const loadExplorerData = useMemo(() => {
|
||||
const service = new AnomalyTimelineService(
|
||||
const anomalyTimelineService = new AnomalyTimelineService(
|
||||
timefilter,
|
||||
uiSettings,
|
||||
mlResultsServiceProvider(mlApiServices)
|
||||
);
|
||||
return loadExplorerDataProvider(service);
|
||||
return loadExplorerDataProvider(anomalyTimelineService, timefilter);
|
||||
}, []);
|
||||
|
||||
const loadExplorerData$ = useMemo(() => new Subject<LoadExplorerDataConfig>(), []);
|
||||
|
|
|
@ -20,7 +20,6 @@ export const EXPLORER_ACTION = {
|
|||
CLEAR_INFLUENCER_FILTER_SETTINGS: 'clearInfluencerFilterSettings',
|
||||
CLEAR_JOBS: 'clearJobs',
|
||||
JOB_SELECTION_CHANGE: 'jobSelectionChange',
|
||||
SET_BOUNDS: 'setBounds',
|
||||
SET_CHARTS: 'setCharts',
|
||||
SET_EXPLORER_DATA: 'setExplorerData',
|
||||
SET_FILTER_DATA: 'setFilterData',
|
||||
|
|
|
@ -19,7 +19,7 @@ import { DeepPartial } from '../../../common/types/common';
|
|||
import { jobSelectionActionCreator } from './actions';
|
||||
import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_service';
|
||||
import { EXPLORER_ACTION } from './explorer_constants';
|
||||
import { AppStateSelectedCells, TimeRangeBounds } from './explorer_utils';
|
||||
import { AppStateSelectedCells } from './explorer_utils';
|
||||
import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers';
|
||||
import { ExplorerAppState } from '../../../common/types/ml_url_generator';
|
||||
|
||||
|
@ -115,9 +115,6 @@ export const explorerService = {
|
|||
updateJobSelection: (selectedJobIds: string[]) => {
|
||||
explorerAction$.next(jobSelectionActionCreator(selectedJobIds));
|
||||
},
|
||||
setBounds: (payload: TimeRangeBounds) => {
|
||||
explorerAction$.next({ type: EXPLORER_ACTION.SET_BOUNDS, payload });
|
||||
},
|
||||
setCharts: (payload: ExplorerChartsData) => {
|
||||
explorerAction$.next({ type: EXPLORER_ACTION.SET_CHARTS, payload });
|
||||
},
|
||||
|
|
|
@ -186,7 +186,7 @@ export declare interface FilterData {
|
|||
export declare interface AppStateSelectedCells {
|
||||
type: SwimlaneType;
|
||||
lanes: string[];
|
||||
times: number[];
|
||||
times: [number, number];
|
||||
showTopFieldValues?: boolean;
|
||||
viewByFieldName?: string;
|
||||
}
|
||||
|
|
|
@ -7,15 +7,19 @@
|
|||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Duration } from 'moment';
|
||||
import { SWIMLANE_TYPE } from '../explorer_constants';
|
||||
import { AppStateSelectedCells, TimeRangeBounds } from '../explorer_utils';
|
||||
import { AppStateSelectedCells } from '../explorer_utils';
|
||||
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
|
||||
import { useTimefilter } from '../../contexts/kibana';
|
||||
|
||||
export const useSelectedCells = (
|
||||
appState: ExplorerAppState,
|
||||
setAppState: (update: Partial<ExplorerAppState>) => void,
|
||||
timeBounds: TimeRangeBounds | undefined,
|
||||
bucketInterval: Duration | undefined
|
||||
): [AppStateSelectedCells | undefined, (swimlaneSelectedCells: AppStateSelectedCells) => void] => {
|
||||
const timeFilter = useTimefilter();
|
||||
|
||||
const timeBounds = timeFilter.getBounds();
|
||||
|
||||
// keep swimlane selection, restore selectedCells from AppState
|
||||
const selectedCells = useMemo(() => {
|
||||
return appState?.mlExplorerSwimlane?.selectedType !== undefined
|
||||
|
@ -73,12 +77,7 @@ export const useSelectedCells = (
|
|||
* Reset it entirely when it out of range.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (
|
||||
timeBounds === undefined ||
|
||||
selectedCells?.times === undefined ||
|
||||
bucketInterval === undefined
|
||||
)
|
||||
return;
|
||||
if (selectedCells?.times === undefined || bucketInterval === undefined) return;
|
||||
|
||||
let [selectedFrom, selectedTo] = selectedCells.times;
|
||||
|
||||
|
@ -108,7 +107,33 @@ export const useSelectedCells = (
|
|||
times: [selectedFrom, selectedTo],
|
||||
});
|
||||
}
|
||||
}, [timeBounds, selectedCells, bucketInterval]);
|
||||
}, [
|
||||
timeBounds.min?.valueOf(),
|
||||
timeBounds.max?.valueOf(),
|
||||
selectedCells,
|
||||
bucketInterval?.asMilliseconds(),
|
||||
]);
|
||||
|
||||
return [selectedCells, setSelectedCells];
|
||||
};
|
||||
|
||||
export interface SelectionTimeRange {
|
||||
earliestMs: number;
|
||||
latestMs: number;
|
||||
}
|
||||
|
||||
export function getTimeBoundsFromSelection(
|
||||
selectedCells: AppStateSelectedCells | undefined
|
||||
): SelectionTimeRange | undefined {
|
||||
if (selectedCells?.times === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// time property of the cell data is an array, with the elements being
|
||||
// the start times of the first and last cell selected.
|
||||
return {
|
||||
earliestMs: selectedCells.times[0] * 1000,
|
||||
// Subtract 1 ms so search does not include start of next bucket.
|
||||
latestMs: selectedCells.times[1] * 1000 - 1,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Action } from '../../explorer_dashboard_service';
|
|||
import {
|
||||
getClearedSelectedAnomaliesState,
|
||||
getDefaultSwimlaneData,
|
||||
getSelectionTimeRange,
|
||||
getSwimlaneBucketInterval,
|
||||
getViewBySwimlaneOptions,
|
||||
} from '../../explorer_utils';
|
||||
|
@ -23,6 +22,7 @@ import { jobSelectionChange } from './job_selection_change';
|
|||
import { ExplorerState } from './state';
|
||||
import { setInfluencerFilterSettings } from './set_influencer_filter_settings';
|
||||
import { setKqlQueryBarPlaceholder } from './set_kql_query_bar_placeholder';
|
||||
import { getTimeBoundsFromSelection } from '../../hooks/use_selected_cells';
|
||||
|
||||
export const explorerReducer = (state: ExplorerState, nextAction: Action): ExplorerState => {
|
||||
const { type, payload } = nextAction;
|
||||
|
@ -48,10 +48,6 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
|
|||
nextState = jobSelectionChange(state, payload);
|
||||
break;
|
||||
|
||||
case EXPLORER_ACTION.SET_BOUNDS:
|
||||
nextState = { ...state, bounds: payload };
|
||||
break;
|
||||
|
||||
case EXPLORER_ACTION.SET_CHARTS:
|
||||
nextState = {
|
||||
...state,
|
||||
|
@ -144,7 +140,7 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
|
|||
nextState = state;
|
||||
}
|
||||
|
||||
if (nextState.selectedJobs === null || nextState.bounds === undefined) {
|
||||
if (nextState.selectedJobs === null) {
|
||||
return nextState;
|
||||
}
|
||||
|
||||
|
@ -164,21 +160,16 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
|
|||
selectedCells: nextState.selectedCells!,
|
||||
});
|
||||
|
||||
const { bounds, selectedCells } = nextState;
|
||||
const { selectedCells } = nextState;
|
||||
|
||||
const timerange = getSelectionTimeRange(
|
||||
selectedCells,
|
||||
swimlaneBucketInterval.asSeconds(),
|
||||
bounds
|
||||
);
|
||||
const timeRange = getTimeBoundsFromSelection(selectedCells);
|
||||
|
||||
return {
|
||||
...nextState,
|
||||
swimlaneBucketInterval,
|
||||
viewByLoadedForTimeFormatted:
|
||||
selectedCells !== undefined && selectedCells.showTopFieldValues === true
|
||||
? formatHumanReadableDateTime(timerange.earliestMs)
|
||||
: null,
|
||||
viewByLoadedForTimeFormatted: timeRange
|
||||
? formatHumanReadableDateTime(timeRange.earliestMs)
|
||||
: null,
|
||||
viewBySwimlaneFieldName,
|
||||
viewBySwimlaneOptions,
|
||||
...checkSelectedCells(nextState),
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
AnomaliesTableData,
|
||||
ExplorerJob,
|
||||
AppStateSelectedCells,
|
||||
TimeRangeBounds,
|
||||
OverallSwimlaneData,
|
||||
SwimlaneData,
|
||||
ViewBySwimLaneData,
|
||||
|
@ -27,7 +26,6 @@ import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants';
|
|||
|
||||
export interface ExplorerState {
|
||||
annotations: AnnotationsTable;
|
||||
bounds: TimeRangeBounds | undefined;
|
||||
chartsData: ExplorerChartsData;
|
||||
fieldFormatsLoading: boolean;
|
||||
filterActive: boolean;
|
||||
|
@ -69,7 +67,6 @@ export function getExplorerDefaultState(): ExplorerState {
|
|||
annotationsData: [],
|
||||
aggregations: {},
|
||||
},
|
||||
bounds: undefined,
|
||||
chartsData: getDefaultChartsData(),
|
||||
fieldFormatsLoading: false,
|
||||
filterActive: false,
|
||||
|
|
|
@ -255,7 +255,7 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
|
|||
onBrushEnd: (e: HeatmapBrushEvent) => {
|
||||
onCellsSelection({
|
||||
lanes: e.y as string[],
|
||||
times: e.x.map((v) => (v as number) / 1000),
|
||||
times: e.x.map((v) => (v as number) / 1000) as [number, number],
|
||||
type: swimlaneType,
|
||||
viewByFieldName: swimlaneData.fieldName,
|
||||
});
|
||||
|
@ -326,7 +326,7 @@ export const SwimlaneContainer: FC<SwimlaneProps> = ({
|
|||
const startTime = (cell.datum.x as number) / 1000;
|
||||
const payload = {
|
||||
lanes: [String(cell.datum.y)],
|
||||
times: [startTime, startTime + swimlaneData.interval],
|
||||
times: [startTime, startTime + swimlaneData.interval] as [number, number],
|
||||
type: swimlaneType,
|
||||
viewByFieldName: swimlaneData.fieldName,
|
||||
};
|
||||
|
|
|
@ -119,13 +119,6 @@ const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTim
|
|||
from: globalState.time.from,
|
||||
to: globalState.time.to,
|
||||
});
|
||||
|
||||
const timefilterBounds = timefilter.getBounds();
|
||||
// Only if both min/max bounds are valid moment times set the bounds.
|
||||
// An invalid string restored from globalState might return `undefined`.
|
||||
if (timefilterBounds?.min !== undefined && timefilterBounds?.max !== undefined) {
|
||||
explorerService.setBounds(timefilterBounds);
|
||||
}
|
||||
}
|
||||
}, [globalState?.time?.from, globalState?.time?.to]);
|
||||
|
||||
|
@ -208,7 +201,6 @@ const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTim
|
|||
const [selectedCells, setSelectedCells] = useSelectedCells(
|
||||
explorerUrlState,
|
||||
setExplorerUrlState,
|
||||
explorerState?.bounds,
|
||||
explorerState?.swimlaneBucketInterval
|
||||
);
|
||||
|
||||
|
@ -219,7 +211,6 @@ const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTim
|
|||
const loadExplorerDataConfig =
|
||||
explorerState !== undefined
|
||||
? {
|
||||
bounds: explorerState.bounds,
|
||||
lastRefresh,
|
||||
influencersFilterQuery: explorerState.influencersFilterQuery,
|
||||
noInfluencersConfigured: explorerState.noInfluencersConfigured,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue