Skip to content

Commit

Permalink
[ML] Extend MlUrlGenerator to support other ML pages (#75696)
Browse files Browse the repository at this point in the history
Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
  • Loading branch information
qn895 and darnautov committed Sep 2, 2020
1 parent d9dc47e commit aac8424
Show file tree
Hide file tree
Showing 22 changed files with 982 additions and 208 deletions.
38 changes: 38 additions & 0 deletions x-pack/plugins/ml/common/constants/ml_url_generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR';

export const ML_PAGES = {
ANOMALY_DETECTION_JOBS_MANAGE: 'jobs',
ANOMALY_EXPLORER: 'explorer',
SINGLE_METRIC_VIEWER: 'timeseriesexplorer',
DATA_FRAME_ANALYTICS_JOBS_MANAGE: 'data_frame_analytics',
DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration',
/**
* Page: Data Visualizer
*/
DATA_VISUALIZER: 'datavisualizer',
/**
* Page: Data Visualizer
* Open data visualizer by selecting a Kibana index pattern or saved search
*/
DATA_VISUALIZER_INDEX_SELECT: 'datavisualizer_index_select',
/**
* Page: Data Visualizer
* Open data visualizer by importing data from a log file
*/
DATA_VISUALIZER_FILE: 'filedatavisualizer',
/**
* Page: Data Visualizer
* Open index data visualizer viewer page
*/
DATA_VISUALIZER_INDEX_VIEWER: 'jobs/new_job/datavisualizer',
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: `jobs/new_job/step/job_type`,
SETTINGS: 'settings',
CALENDARS_MANAGE: 'settings/calendars_list',
FILTER_LISTS_MANAGE: 'settings/filter_lists',
} as const;
187 changes: 187 additions & 0 deletions x-pack/plugins/ml/common/types/ml_url_generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common/query';
import { JobId } from '../../../reporting/common/types';
import { ML_PAGES } from '../constants/ml_url_generator';

type OptionalPageState = object | undefined;

export type MLPageState<PageType, PageState> = PageState extends OptionalPageState
? { page: PageType; pageState?: PageState }
: PageState extends object
? { page: PageType; pageState: PageState }
: { page: PageType };

export const ANALYSIS_CONFIG_TYPE = {
OUTLIER_DETECTION: 'outlier_detection',
REGRESSION: 'regression',
CLASSIFICATION: 'classification',
} as const;

type DataFrameAnalyticsType = typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE];

export interface MlCommonGlobalState {
time?: TimeRange;
}
export interface MlCommonAppState {
[key: string]: any;
}

export interface MlIndexBasedSearchState {
index?: string;
savedSearchId?: string;
}

export interface MlGenericUrlPageState extends MlIndexBasedSearchState {
globalState?: MlCommonGlobalState;
appState?: MlCommonAppState;
[key: string]: any;
}

export interface MlGenericUrlState {
page:
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE;
pageState: MlGenericUrlPageState;
}

export interface AnomalyDetectionQueryState {
jobId?: JobId;
groupIds?: string[];
}

export type AnomalyDetectionUrlState = MLPageState<
typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
AnomalyDetectionQueryState | undefined
>;
export interface ExplorerAppState {
mlExplorerSwimlane: {
selectedType?: string;
selectedLanes?: string[];
selectedTimes?: number[];
showTopFieldValues?: boolean;
viewByFieldName?: string;
viewByPerPage?: number;
viewByFromPage?: number;
};
mlExplorerFilter: {
influencersFilterQuery?: unknown;
filterActive?: boolean;
filteredFields?: string[];
queryString?: string;
};
query?: any;
}
export interface ExplorerGlobalState {
ml: { jobIds: JobId[] };
time?: TimeRange;
refreshInterval?: RefreshInterval;
}

export interface ExplorerUrlPageState {
/**
* Job IDs
*/
jobIds: JobId[];
/**
* Optionally set the time range in the time picker.
*/
timeRange?: TimeRange;
/**
* Optionally set the refresh interval.
*/
refreshInterval?: RefreshInterval;
/**
* Optionally set the query.
*/
query?: any;
/**
* Optional state for the swim lane
*/
mlExplorerSwimlane?: ExplorerAppState['mlExplorerSwimlane'];
mlExplorerFilter?: ExplorerAppState['mlExplorerFilter'];
}

export type ExplorerUrlState = MLPageState<typeof ML_PAGES.ANOMALY_EXPLORER, ExplorerUrlPageState>;

export interface TimeSeriesExplorerGlobalState {
ml: {
jobIds: JobId[];
};
time?: TimeRange;
refreshInterval?: RefreshInterval;
}

export interface TimeSeriesExplorerAppState {
zoom?: {
from?: string;
to?: string;
};
mlTimeSeriesExplorer?: {
detectorIndex?: number;
entities?: Record<string, string>;
};
query?: any;
}

export interface TimeSeriesExplorerPageState
extends Pick<TimeSeriesExplorerAppState, 'zoom' | 'query'>,
Pick<TimeSeriesExplorerGlobalState, 'refreshInterval'> {
jobIds: JobId[];
timeRange?: TimeRange;
detectorIndex?: number;
entities?: Record<string, string>;
}

export type TimeSeriesExplorerUrlState = MLPageState<
typeof ML_PAGES.SINGLE_METRIC_VIEWER,
TimeSeriesExplorerPageState
>;

export interface DataFrameAnalyticsQueryState {
jobId?: JobId | JobId[];
groupIds?: string[];
}

export type DataFrameAnalyticsUrlState = MLPageState<
typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
DataFrameAnalyticsQueryState | undefined
>;

export interface DataVisualizerUrlState {
page:
| typeof ML_PAGES.DATA_VISUALIZER
| typeof ML_PAGES.DATA_VISUALIZER_FILE
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT;
}

export interface DataFrameAnalyticsExplorationQueryState {
ml: {
jobId: JobId;
analysisType: DataFrameAnalyticsType;
};
}

export type DataFrameAnalyticsExplorationUrlState = MLPageState<
typeof ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
{
jobId: JobId;
analysisType: DataFrameAnalyticsType;
}
>;

/**
* Union type of ML URL state based on page
*/
export type MlUrlGeneratorState =
| AnomalyDetectionUrlState
| ExplorerUrlState
| TimeSeriesExplorerUrlState
| DataFrameAnalyticsUrlState
| DataFrameAnalyticsExplorationUrlState
| DataVisualizerUrlState
| MlGenericUrlState;
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { useNavigateToPath, NavigateToPath } from './use_navigate_to_path';
export { useUiSettings } from './use_ui_settings_context';
export { useTimefilter } from './use_timefilter';
export { useNotifications } from './use_notifications_context';
export { useMlUrlGenerator } from './use_create_url';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useMlKibana } from './kibana_context';
import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator';

export const useMlUrlGenerator = () => {
const {
services: {
share: {
urlGenerators: { getUrlGenerator },
},
},
} = useMlKibana();

return getUrlGenerator(ML_APP_URL_GENERATOR);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@ export const useNavigateToPath = () => {

const location = useLocation();

return useMemo(
() => (path: string | undefined, preserveSearch = false) => {
navigateToUrl(
getUrlForApp(PLUGIN_ID, {
path: `${path}${preserveSearch === true ? location.search : ''}`,
})
);
},
[location]
);
return useMemo(() => {
return (path: string | undefined, preserveSearch = false) => {
if (path === undefined) return;
const modifiedPath = `${path}${preserveSearch === true ? location.search : ''}`;
/**
* Handle urls generated by MlUrlGenerator where '/app/ml' is automatically prepended
*/
const url = modifiedPath.includes('/app/ml')
? modifiedPath
: getUrlForApp(PLUGIN_ID, {
path: modifiedPath,
});
navigateToUrl(url);
};
}, [location]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@
import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlKibana, useMlUrlGenerator } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';

export const BackToListPanel: FC = () => {
const navigateToPath = useNavigateToPath();
const urlGenerator = useMlUrlGenerator();
const {
services: {
application: { navigateToUrl },
},
} = useMlKibana();

const redirectToAnalyticsManagementPage = async () => {
await navigateToPath('/data_frame_analytics');
const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
await navigateToUrl(url);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@
import React, { FC, Fragment } from 'react';
import { EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { getResultsUrl } from '../../../analytics_management/components/analytics_list/common';
import { useMlUrlGenerator } from '../../../../../contexts/kibana';
import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';

import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { useNavigateToPath } from '../../../../../contexts/kibana';
interface Props {
jobId: string;
analysisType: ANALYSIS_CONFIG_TYPE;
}

export const ViewResultsPanel: FC<Props> = ({ jobId, analysisType }) => {
const urlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();

const redirectToAnalyticsManagementPage = async () => {
const path = getResultsUrl(jobId, analysisType);
const redirectToAnalyticsExplorationPage = async () => {
const path = await urlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId,
analysisType,
},
});
await navigateToPath(path);
};

Expand All @@ -38,7 +45,7 @@ export const ViewResultsPanel: FC<Props> = ({ jobId, analysisType }) => {
defaultMessage: 'View results for the analytics job.',
}
)}
onClick={redirectToAnalyticsManagementPage}
onClick={redirectToAnalyticsExplorationPage}
data-test-subj="analyticsWizardViewResultsCard"
/>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_
import { EXPLORER_ACTION } from './explorer_constants';
import { AppStateSelectedCells, TimeRangeBounds } from './explorer_utils';
import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers';
import { ExplorerAppState } from '../../../common/types/ml_url_generator';

export const ALLOW_CELL_RANGE_SELECTION = true;

Expand Down Expand Up @@ -49,24 +50,6 @@ const explorerState$: Observable<ExplorerState> = explorerFilteredAction$.pipe(
shareReplay(1)
);

export interface ExplorerAppState {
mlExplorerSwimlane: {
selectedType?: string;
selectedLanes?: string[];
selectedTimes?: number[];
showTopFieldValues?: boolean;
viewByFieldName?: string;
viewByPerPage?: number;
viewByFromPage?: number;
};
mlExplorerFilter: {
influencersFilterQuery?: unknown;
filterActive?: boolean;
filteredFields?: string[];
queryString?: string;
};
}

const explorerAppState$: Observable<ExplorerAppState> = explorerState$.pipe(
map(
(state: ExplorerState): ExplorerAppState => {
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/ml/public/application/routing/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ const MlRoutes: FC<{
pageDeps: PageDependencies;
}> = ({ pageDeps }) => {
const navigateToPath = useNavigateToPath();

return (
<>
{Object.entries(routes).map(([name, routeFactory]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import { useSelectedCells } from '../../explorer/hooks/use_selected_cells';
import { mlJobService } from '../../services/job_service';
import { ml } from '../../services/ml_api_service';
import { useExplorerData } from '../../explorer/actions';
import { ExplorerAppState, explorerService } from '../../explorer/explorer_dashboard_service';
import { ExplorerAppState } from '../../../../common/types/ml_url_generator';
import { explorerService } from '../../explorer/explorer_dashboard_service';
import { getDateFormatTz } from '../../explorer/explorer_utils';
import { useJobSelection } from '../../components/job_selector/use_job_selection';
import { useShowCharts } from '../../components/controls/checkbox_showcharts';
Expand Down
Loading

0 comments on commit aac8424

Please sign in to comment.