Skip to content

Commit

Permalink
[ML] Anomaly Explorer: Hide top influencers panel for jobs without in…
Browse files Browse the repository at this point in the history
…fluencers (elastic#192987)

# Summary

Fix for [elastic#192679](elastic#192679)
Hiding the top influencers panel when there are no influencers for the
selected job.
Added a functional test to ensure the panel is hidden.
Expanded a few types to improve type safety.

(cherry picked from commit 23b2595)
  • Loading branch information
rbrtj committed Sep 20, 2024
1 parent 297ab0b commit 6a9e903
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 441 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
* 2.0.
*/

import type { Observable } from 'rxjs';
import { from } from 'rxjs';
import { map } from 'rxjs';

import type { MlFieldFormatService } from '../../services/field_format_service';
import type { MlJobService } from '../../services/job_service';

import { EXPLORER_ACTION } from '../explorer_constants';
import { createJobs } from '../explorer_utils';
import { createJobs, getInfluencers } from '../explorer_utils';
import type { ExplorerActions } from '../explorer_dashboard_service';

export function jobSelectionActionCreator(
mlJobService: MlJobService,
mlFieldFormatService: MlFieldFormatService,
selectedJobIds: string[]
) {
): Observable<ExplorerActions | null> {
return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe(
map((resp) => {
if (resp.error) {
Expand All @@ -31,12 +33,14 @@ export function jobSelectionActionCreator(
});

const selectedJobs = jobs.filter((job) => job.selected);
const noInfluencersConfigured = getInfluencers(mlJobService, selectedJobs).length === 0;

return {
type: EXPLORER_ACTION.JOB_SELECTION_CHANGE,
payload: {
loading: false,
selectedJobs,
noInfluencersConfigured,
},
};
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const EXPLORER_ACTION = {
JOB_SELECTION_CHANGE: 'jobSelectionChange',
SET_CHARTS_DATA_LOADING: 'setChartsDataLoading',
SET_EXPLORER_DATA: 'setExplorerData',
};
} as const;

export const FILTER_ACTION = {
ADD: '+',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,38 @@ import type { ExplorerState } from './reducers';
import { explorerReducer, getExplorerDefaultState } from './reducers';
import type { MlFieldFormatService } from '../services/field_format_service';
import type { MlJobService } from '../services/job_service';
import type { ExplorerJob } from './explorer_utils';

type ExplorerAction = Action | Observable<ActionPayload>;
export const explorerAction$ = new Subject<ExplorerAction>();
type ExplorerAction = (typeof EXPLORER_ACTION)[keyof typeof EXPLORER_ACTION];

export type ActionPayload = any;

export interface Action {
type: string;
payload?: ActionPayload;
export interface ExplorerActionPayloads {
[EXPLORER_ACTION.SET_EXPLORER_DATA]: DeepPartial<ExplorerState>;
[EXPLORER_ACTION.JOB_SELECTION_CHANGE]: {
loading: boolean;
selectedJobs: ExplorerJob[];
noInfluencersConfigured: boolean;
};
}

export type ExplorerActions = {
[K in ExplorerAction]: K extends keyof ExplorerActionPayloads
? {
type: K;
payload: ExplorerActionPayloads[K];
}
: {
type: K;
};
}[ExplorerAction];

type ExplorerActionMaybeObservable = ExplorerActions | Observable<ExplorerActions | null>;

export const explorerAction$ = new Subject<ExplorerActionMaybeObservable>();

const explorerFilteredAction$ = explorerAction$.pipe(
// consider observables as side-effects
flatMap((action: ExplorerAction) =>
isObservable(action) ? action : (from([action]) as Observable<ExplorerAction>)
flatMap((action: ExplorerActionMaybeObservable) =>
isObservable(action) ? action : (from([action]) as Observable<ExplorerActionMaybeObservable>)
),
distinctUntilChanged(isEqual)
);
Expand All @@ -47,11 +64,6 @@ const explorerState$: Observable<ExplorerState> = explorerFilteredAction$.pipe(
shareReplay(1)
);

const setExplorerDataActionCreator = (payload: DeepPartial<ExplorerState>) => ({
type: EXPLORER_ACTION.SET_EXPLORER_DATA,
payload,
});

// Export observable state and action dispatchers as service
export const explorerServiceFactory = (
mlJobService: MlJobService,
Expand All @@ -62,7 +74,9 @@ export const explorerServiceFactory = (
explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_EXPLORER_DATA });
},
clearInfluencerFilterSettings: () => {
explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_INFLUENCER_FILTER_SETTINGS });
explorerAction$.next({
type: EXPLORER_ACTION.CLEAR_INFLUENCER_FILTER_SETTINGS,
});
},
clearJobs: () => {
explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_JOBS });
Expand All @@ -73,7 +87,7 @@ export const explorerServiceFactory = (
);
},
setExplorerData: (payload: DeepPartial<ExplorerState>) => {
explorerAction$.next(setExplorerDataActionCreator(payload));
explorerAction$.next({ type: EXPLORER_ACTION.SET_EXPLORER_DATA, payload });
},
setChartsDataLoading: () => {
explorerAction$.next({ type: EXPLORER_ACTION.SET_CHARTS_DATA_LOADING });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
*/

import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
import type { ExplorerJob } from '../../explorer_utils';

// Creates index pattern in the format expected by the kuery bar/kuery autocomplete provider
// Field objects required fields: name, type, aggregatable, searchable
export function getIndexPattern(influencers: string[]) {
export function getIndexPattern(influencers: ExplorerJob[]) {
return {
title: ML_RESULTS_INDEX_PATTERN,
fields: influencers.map((influencer) => ({
name: influencer,
name: influencer.id,
type: 'string',
aggregatable: true,
searchable: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
* 2.0.
*/

import type { ActionPayload } from '../../explorer_dashboard_service';
import type { EXPLORER_ACTION } from '../../explorer_constants';
import type { ExplorerActionPayloads } from '../../explorer_dashboard_service';

import { getIndexPattern } from './get_index_pattern';
import type { ExplorerState } from './state';

export const jobSelectionChange = (state: ExplorerState, payload: ActionPayload): ExplorerState => {
export const jobSelectionChange = (
state: ExplorerState,
payload: ExplorerActionPayloads[typeof EXPLORER_ACTION.JOB_SELECTION_CHANGE]
): ExplorerState => {
const { selectedJobs, noInfluencersConfigured } = payload;
const stateUpdate: ExplorerState = {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { getDefaultChartsData } from '../../explorer_charts/explorer_charts_container_service';
import { EXPLORER_ACTION } from '../../explorer_constants';
import type { Action } from '../../explorer_dashboard_service';
import type { ExplorerActionPayloads, ExplorerActions } from '../../explorer_dashboard_service';
import { getClearedSelectedAnomaliesState } from '../../explorer_utils';

import { clearInfluencerFilterSettings } from './clear_influencer_filter_settings';
Expand All @@ -16,8 +16,12 @@ import type { ExplorerState } from './state';
import { getExplorerDefaultState } from './state';
import { setKqlQueryBarPlaceholder } from './set_kql_query_bar_placeholder';

export const explorerReducer = (state: ExplorerState, nextAction: Action): ExplorerState => {
const { type, payload } = nextAction;
export const explorerReducer = (
state: ExplorerState,
nextAction: ExplorerActions
): ExplorerState => {
const { type } = nextAction;
const payload = 'payload' in nextAction ? nextAction.payload : {};

let nextState: ExplorerState;

Expand All @@ -40,7 +44,10 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
break;

case EXPLORER_ACTION.JOB_SELECTION_CHANGE:
nextState = jobSelectionChange(state, payload);
nextState = jobSelectionChange(
state,
payload as ExplorerActionPayloads[typeof EXPLORER_ACTION.JOB_SELECTION_CHANGE]
);
break;

case EXPLORER_ACTION.SET_CHARTS_DATA_LOADING:
Expand All @@ -52,7 +59,7 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo
break;

case EXPLORER_ACTION.SET_EXPLORER_DATA:
nextState = { ...state, ...payload };
nextState = { ...state, ...(payload as Partial<ExplorerState>) };
break;

default:
Expand Down
Loading

0 comments on commit 6a9e903

Please sign in to comment.