Skip to content

Commit

Permalink
[ML] Simplify data fetching. Expose explorer observable state as serv…
Browse files Browse the repository at this point in the history
…ice.
  • Loading branch information
walterra committed Nov 28, 2019
1 parent 1aa8453 commit 6bad98d
Show file tree
Hide file tree
Showing 23 changed files with 877 additions and 604 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
*/

export { jobSelectionActionCreator } from './job_selection';
export { loadExplorerDataActionCreator } from './load_explorer_data';
export { loadExplorerData } from './load_explorer_data';
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import { map } from 'rxjs/operators';
import { mlFieldFormatService } from '../../services/field_format_service';
import { mlJobService } from '../../services/job_service';

import { ExplorerAppState } from '../reducers';
import { createJobs, restoreAppState } from '../explorer_utils';
import { createJobs, RestoredAppState } from '../explorer_utils';

export function jobSelectionActionCreator(
actionName: string,
selectedJobIds: string[],
appState: ExplorerAppState
{ filterData, selectedCells, viewBySwimlaneFieldName }: RestoredAppState
) {
return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe(
map(resp => {
Expand All @@ -25,8 +24,6 @@ export function jobSelectionActionCreator(
return null;
}

const { selectedCells, filterData } = restoreAppState(appState);

const jobs = createJobs(mlJobService.jobs).map(job => {
job.selected = selectedJobIds.some(id => job.id === id);
return job;
Expand All @@ -40,7 +37,7 @@ export function jobSelectionActionCreator(
loading: false,
selectedCells,
selectedJobs,
viewBySwimlaneFieldName: appState.mlExplorerSwimlane.viewByFieldName,
viewBySwimlaneFieldName,
filterData,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,16 @@
import memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';

import { forkJoin } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';

import { i18n } from '@kbn/i18n';

import { formatHumanReadableDateTime } from '../../util/date_utils';
import { forkJoin, of } from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';

import { explorerChartsContainerServiceFactory } from '../explorer_charts/explorer_charts_container_service';
import { VIEW_BY_JOB_LABEL } from '../explorer_constants';
import { explorerService } from '../explorer_dashboard_service';
import {
explorerChartsContainerServiceFactory,
getDefaultChartsData,
} from '../explorer_charts/explorer_charts_container_service';
import { EXPLORER_ACTION, SWIMLANE_TYPE, VIEW_BY_JOB_LABEL } from '../explorer_constants';
import { explorerAction$ } from '../explorer_dashboard_service';
import {
getClearedSelectedAnomaliesState,
getDateFormatTz,
getDefaultSwimlaneData,
getSelectionInfluencers,
getSelectionTimeRange,
getSwimlaneBucketInterval,
getViewBySwimlaneOptions,
loadAnnotationsTableData,
loadAnomaliesTableData,
loadDataForCharts,
Expand All @@ -39,11 +28,6 @@ import {
} from '../explorer_utils';
import { ExplorerState } from '../reducers';

interface SwimlanePoint {
laneLabel: string;
time: number;
}

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

// Memoize the data fetching methods
Expand All @@ -65,57 +49,28 @@ const dateFormatTz = getDateFormatTz();
*
* @param state ExplorerState
*
* @return action observable
* @return Partial<ExplorerState>
*/
export function loadExplorerDataActionCreator(state: ExplorerState) {
export function loadExplorerData(state: ExplorerState) {
const {
bounds,
filterActive,
filteredFields,
influencersFilterQuery,
isAndOperator,
noInfluencersConfigured,
selectedCells,
selectedJobs,
swimlaneContainerWidth,
swimlaneBucketInterval,
swimlaneLimit,
tableInterval,
tableSeverity,
viewBySwimlaneFieldName: currentViewBySwimlaneFieldName,
viewBySwimlaneFieldName,
} = state;

if (selectedJobs === null) {
return null;
if (selectedJobs === null || bounds === undefined || viewBySwimlaneFieldName === undefined) {
return of({});
}

const swimlaneBucketInterval = getSwimlaneBucketInterval(selectedJobs, swimlaneContainerWidth);

// TODO This factory should be refactored so we can load the charts using memoization.
const updateCharts = explorerChartsContainerServiceFactory(data => {
explorerAction$.next({
type: EXPLORER_ACTION.SET_STATE,
payload: {
chartsData: {
...getDefaultChartsData(),
chartsPerRow: data.chartsPerRow,
seriesToPlot: data.seriesToPlot,
// convert truthy/falsy value to Boolean
tooManyBuckets: !!data.tooManyBuckets,
},
},
});
});

// Does a sanity check on the selected `currentViewBySwimlaneFieldName`
// and returns the available `viewBySwimlaneOptions`.
const { viewBySwimlaneFieldName, viewBySwimlaneOptions } = getViewBySwimlaneOptions({
currentViewBySwimlaneFieldName,
filterActive,
filteredFields,
isAndOperator,
selectedJobs,
selectedCells,
});
const updateCharts = explorerChartsContainerServiceFactory(explorerService.setCharts);

const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName);

Expand All @@ -130,19 +85,6 @@ export function loadExplorerDataActionCreator(state: ExplorerState) {
bounds
);

// Trigger a side effect to pass on updated information to the state.
explorerAction$.next({
type: EXPLORER_ACTION.SET_STATE,
payload: {
viewByLoadedForTimeFormatted:
selectedCells !== null && selectedCells.showTopFieldValues === true
? formatHumanReadableDateTime(timerange.earliestMs)
: null,
viewBySwimlaneFieldName,
viewBySwimlaneOptions,
},
});

// First get the data where we have all necessary args at hand using forkJoin:
// annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues
return forkJoin({
Expand Down Expand Up @@ -198,21 +140,7 @@ export function loadExplorerDataActionCreator(state: ExplorerState) {
// Trigger a side-effect action to reset view-by swimlane,
// show the view-by loading indicator
// and pass on the data we already fetched.
tap(({ annotationsData, overallState, tableData }) => {
explorerAction$.next({
type: EXPLORER_ACTION.SET_STATE,
payload: {
annotationsData,
overallState,
tableData,
viewBySwimlaneData: {
...getDefaultSwimlaneData(),
fieldName: viewBySwimlaneFieldName,
},
viewBySwimlaneDataLoading: true,
},
});
}),
tap(explorerService.setViewBySwimlaneLoading),
// Trigger a side-effect to update the charts.
tap(({ anomalyChartRecords }) => {
if (selectedCells !== null && Array.isArray(anomalyChartRecords)) {
Expand Down Expand Up @@ -257,71 +185,13 @@ export function loadExplorerDataActionCreator(state: ExplorerState) {
}),
({ annotationsData, overallState, tableData }, { influencers, viewBySwimlaneState }) => {
return {
type: EXPLORER_ACTION.SET_STATE,
payload: {
annotationsData,
influencers,
...overallState,
...viewBySwimlaneState,
tableData,
},
annotationsData,
influencers,
...overallState,
...viewBySwimlaneState,
tableData,
};
}
),
// do a sanity check against selectedCells. It can happen that a previously
// selected lane loaded via URL/AppState is not available anymore.
// If filter is active - selectedCell may not be available due to swimlane view by change to filter fieldName
// Ok to keep cellSelection in this case
map(action => {
const { viewBySwimlaneData } = action.payload;

let clearSelection = false;
if (selectedCells !== null && selectedCells.type === SWIMLANE_TYPE.VIEW_BY) {
clearSelection =
filterActive === false &&
!selectedCells.lanes.some((lane: string) => {
return viewBySwimlaneData.points.some((point: SwimlanePoint) => {
return (
point.laneLabel === lane &&
point.time >= selectedCells.times[0] &&
point.time <= selectedCells.times[1]
);
});
});
}

if (clearSelection === true) {
explorerAction$.next({ type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION });
action.payload = { ...action.payload, ...getClearedSelectedAnomaliesState() };
}

return action;
}),
// Set the KQL query bar placeholder value
map(action => {
const { influencers } = action.payload;

if (influencers !== undefined && !noInfluencersConfigured) {
for (const influencerName in influencers) {
if (
influencers[influencerName][0] &&
influencers[influencerName][0].influencerFieldValue
) {
action.payload.filterPlaceHolder = i18n.translate(
'xpack.ml.explorer.kueryBar.filterPlaceholder',
{
defaultMessage: 'Filter by influencer fields… ({queryExample})',
values: {
queryExample: `${influencerName} : ${influencers[influencerName][0].influencerFieldValue}`,
},
}
);
break;
}
}
}

return action;
})
)
);
}
Loading

0 comments on commit 6bad98d

Please sign in to comment.