mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] Extend MlUrlGenerator to support other ML pages (#75696)
Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
This commit is contained in:
parent
d9dc47ef36
commit
aac84240b2
22 changed files with 982 additions and 208 deletions
38
x-pack/plugins/ml/common/constants/ml_url_generator.ts
Normal file
38
x-pack/plugins/ml/common/constants/ml_url_generator.ts
Normal file
|
@ -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
x-pack/plugins/ml/common/types/ml_url_generator.ts
Normal file
187
x-pack/plugins/ml/common/types/ml_url_generator.ts
Normal file
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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 => {
|
||||
|
|
|
@ -75,7 +75,6 @@ const MlRoutes: FC<{
|
|||
pageDeps: PageDependencies;
|
||||
}> = ({ pageDeps }) => {
|
||||
const navigateToPath = useNavigateToPath();
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(routes).map(([name, routeFactory]) => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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 isEmpty from 'lodash/isEmpty';
|
||||
import {
|
||||
AnomalyDetectionQueryState,
|
||||
AnomalyDetectionUrlState,
|
||||
ExplorerAppState,
|
||||
ExplorerGlobalState,
|
||||
ExplorerUrlState,
|
||||
MlGenericUrlState,
|
||||
TimeSeriesExplorerAppState,
|
||||
TimeSeriesExplorerGlobalState,
|
||||
TimeSeriesExplorerUrlState,
|
||||
} from '../../common/types/ml_url_generator';
|
||||
import { ML_PAGES } from '../../common/constants/ml_url_generator';
|
||||
import { createIndexBasedMlUrl } from './common';
|
||||
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
|
||||
/**
|
||||
* Creates URL to the Anomaly Detection Job management page
|
||||
*/
|
||||
export function createAnomalyDetectionJobManagementUrl(
|
||||
appBasePath: string,
|
||||
params: AnomalyDetectionUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`;
|
||||
if (!params || isEmpty(params)) {
|
||||
return url;
|
||||
}
|
||||
const { jobId, groupIds } = params;
|
||||
const queryState: AnomalyDetectionQueryState = {
|
||||
jobId,
|
||||
groupIds,
|
||||
};
|
||||
|
||||
url = setStateToKbnUrl<AnomalyDetectionQueryState>(
|
||||
'mlManagement',
|
||||
queryState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
return url;
|
||||
}
|
||||
|
||||
export function createAnomalyDetectionCreateJobSelectType(
|
||||
appBasePath: string,
|
||||
pageState: MlGenericUrlState['pageState']
|
||||
): string {
|
||||
return createIndexBasedMlUrl(
|
||||
appBasePath,
|
||||
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
|
||||
pageState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates URL to the Anomaly Explorer page
|
||||
*/
|
||||
export function createExplorerUrl(
|
||||
appBasePath: string,
|
||||
params: ExplorerUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.ANOMALY_EXPLORER}`;
|
||||
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
const {
|
||||
refreshInterval,
|
||||
timeRange,
|
||||
jobIds,
|
||||
query,
|
||||
mlExplorerSwimlane = {},
|
||||
mlExplorerFilter = {},
|
||||
} = params;
|
||||
const appState: Partial<ExplorerAppState> = {
|
||||
mlExplorerSwimlane,
|
||||
mlExplorerFilter,
|
||||
};
|
||||
if (query) appState.query = query;
|
||||
|
||||
if (jobIds) {
|
||||
const queryState: Partial<ExplorerGlobalState> = {
|
||||
ml: {
|
||||
jobIds,
|
||||
},
|
||||
};
|
||||
|
||||
if (timeRange) queryState.time = timeRange;
|
||||
if (refreshInterval) queryState.refreshInterval = refreshInterval;
|
||||
|
||||
url = setStateToKbnUrl<Partial<ExplorerGlobalState>>(
|
||||
'_g',
|
||||
queryState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
url = setStateToKbnUrl<Partial<ExplorerAppState>>(
|
||||
'_a',
|
||||
appState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates URL to the SingleMetricViewer page
|
||||
*/
|
||||
export function createSingleMetricViewerUrl(
|
||||
appBasePath: string,
|
||||
params: TimeSeriesExplorerUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.SINGLE_METRIC_VIEWER}`;
|
||||
if (!params) {
|
||||
return url;
|
||||
}
|
||||
const { timeRange, jobIds, refreshInterval, zoom, query, detectorIndex, entities } = params;
|
||||
|
||||
const queryState: TimeSeriesExplorerGlobalState = {
|
||||
ml: {
|
||||
jobIds,
|
||||
},
|
||||
refreshInterval,
|
||||
time: timeRange,
|
||||
};
|
||||
|
||||
const appState: Partial<TimeSeriesExplorerAppState> = {};
|
||||
const mlTimeSeriesExplorer: Partial<TimeSeriesExplorerAppState['mlTimeSeriesExplorer']> = {};
|
||||
|
||||
if (detectorIndex !== undefined) {
|
||||
mlTimeSeriesExplorer.detectorIndex = detectorIndex;
|
||||
}
|
||||
if (entities !== undefined) {
|
||||
mlTimeSeriesExplorer.entities = entities;
|
||||
}
|
||||
appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer;
|
||||
|
||||
if (zoom) appState.zoom = zoom;
|
||||
if (query)
|
||||
appState.query = {
|
||||
query_string: query,
|
||||
};
|
||||
url = setStateToKbnUrl<TimeSeriesExplorerGlobalState>(
|
||||
'_g',
|
||||
queryState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
url = setStateToKbnUrl<TimeSeriesExplorerAppState>(
|
||||
'_a',
|
||||
appState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
|
||||
return url;
|
||||
}
|
55
x-pack/plugins/ml/public/ml_url_generator/common.ts
Normal file
55
x-pack/plugins/ml/public/ml_url_generator/common.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 isEmpty from 'lodash/isEmpty';
|
||||
import { MlGenericUrlState } from '../../common/types/ml_url_generator';
|
||||
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export function extractParams<UrlState>(urlState: UrlState) {
|
||||
// page should be guaranteed to exist here but <UrlState> is unknown
|
||||
// @ts-ignore
|
||||
const { page, ...params } = urlState;
|
||||
return { page, params };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates generic index based search ML url
|
||||
* e.g. `jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a`
|
||||
*/
|
||||
export function createIndexBasedMlUrl(
|
||||
appBasePath: string,
|
||||
page: MlGenericUrlState['page'],
|
||||
pageState: MlGenericUrlState['pageState']
|
||||
): string {
|
||||
const { globalState, appState, index, savedSearchId, ...restParams } = pageState;
|
||||
let url = `${appBasePath}/${page}`;
|
||||
|
||||
if (index !== undefined && savedSearchId === undefined) {
|
||||
url = `${url}?index=${index}`;
|
||||
}
|
||||
if (index === undefined && savedSearchId !== undefined) {
|
||||
url = `${url}?savedSearchId=${savedSearchId}`;
|
||||
}
|
||||
|
||||
if (!isEmpty(restParams)) {
|
||||
Object.keys(restParams).forEach((key) => {
|
||||
url = setStateToKbnUrl(
|
||||
key,
|
||||
restParams[key],
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (globalState) {
|
||||
url = setStateToKbnUrl('_g', globalState, { useHash: false, storeInHashQuery: false }, url);
|
||||
}
|
||||
if (appState) {
|
||||
url = setStateToKbnUrl('_a', appState, { useHash: false, storeInHashQuery: false }, url);
|
||||
}
|
||||
return url;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates URL to the DataFrameAnalytics page
|
||||
*/
|
||||
import {
|
||||
DataFrameAnalyticsExplorationQueryState,
|
||||
DataFrameAnalyticsExplorationUrlState,
|
||||
DataFrameAnalyticsQueryState,
|
||||
DataFrameAnalyticsUrlState,
|
||||
} from '../../common/types/ml_url_generator';
|
||||
import { ML_PAGES } from '../../common/constants/ml_url_generator';
|
||||
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export function createDataFrameAnalyticsJobManagementUrl(
|
||||
appBasePath: string,
|
||||
mlUrlGeneratorState: DataFrameAnalyticsUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`;
|
||||
|
||||
if (mlUrlGeneratorState) {
|
||||
const { jobId, groupIds } = mlUrlGeneratorState;
|
||||
const queryState: Partial<DataFrameAnalyticsQueryState> = {
|
||||
jobId,
|
||||
groupIds,
|
||||
};
|
||||
|
||||
url = setStateToKbnUrl<Partial<DataFrameAnalyticsQueryState>>(
|
||||
'mlManagement',
|
||||
queryState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates URL to the DataFrameAnalytics Exploration page
|
||||
*/
|
||||
export function createDataFrameAnalyticsExplorationUrl(
|
||||
appBasePath: string,
|
||||
mlUrlGeneratorState: DataFrameAnalyticsExplorationUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`;
|
||||
|
||||
if (mlUrlGeneratorState) {
|
||||
const { jobId, analysisType } = mlUrlGeneratorState;
|
||||
const queryState: DataFrameAnalyticsExplorationQueryState = {
|
||||
ml: {
|
||||
jobId,
|
||||
analysisType,
|
||||
},
|
||||
};
|
||||
|
||||
url = setStateToKbnUrl<DataFrameAnalyticsExplorationQueryState>(
|
||||
'_g',
|
||||
queryState,
|
||||
{ useHash: false, storeInHashQuery: false },
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates URL to the Data Visualizer page
|
||||
*/
|
||||
import { DataVisualizerUrlState, MlGenericUrlState } from '../../common/types/ml_url_generator';
|
||||
import { createIndexBasedMlUrl } from './common';
|
||||
import { ML_PAGES } from '../../common/constants/ml_url_generator';
|
||||
|
||||
export function createDataVisualizerUrl(
|
||||
appBasePath: string,
|
||||
{ page }: DataVisualizerUrlState
|
||||
): string {
|
||||
return `${appBasePath}/${page}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates URL to the Index Data Visualizer
|
||||
*/
|
||||
export function createIndexDataVisualizerUrl(
|
||||
appBasePath: string,
|
||||
pageState: MlGenericUrlState['pageState']
|
||||
): string {
|
||||
return createIndexBasedMlUrl(appBasePath, ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, pageState);
|
||||
}
|
6
x-pack/plugins/ml/public/ml_url_generator/index.ts
Normal file
6
x-pack/plugins/ml/public/ml_url_generator/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* 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 { MlUrlGenerator, registerUrlGenerator } from './ml_url_generator';
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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 { MlUrlGenerator } from './ml_url_generator';
|
||||
import { ML_PAGES } from '../../common/constants/ml_url_generator';
|
||||
import { ANALYSIS_CONFIG_TYPE } from '../../common/types/ml_url_generator';
|
||||
|
||||
describe('MlUrlGenerator', () => {
|
||||
const urlGenerator = new MlUrlGenerator({
|
||||
appBasePath: '/app/ml',
|
||||
useHash: false,
|
||||
});
|
||||
|
||||
describe('AnomalyDetection', () => {
|
||||
describe('Job Management Page', () => {
|
||||
it('should generate valid URL for the Anomaly Detection job management page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
});
|
||||
expect(url).toBe('/app/ml/jobs');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Anomaly Detection job management page for job', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
pageState: {
|
||||
jobId: 'fq_single_1',
|
||||
},
|
||||
});
|
||||
expect(url).toBe('/app/ml/jobs?mlManagement=(jobId:fq_single_1)');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
pageState: {
|
||||
groupIds: ['farequote', 'categorization'],
|
||||
},
|
||||
});
|
||||
expect(url).toBe('/app/ml/jobs?mlManagement=(groupIds:!(farequote,categorization))');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the page for selecting the type of anomaly detection job to create', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
|
||||
pageState: {
|
||||
index: `3da93760-e0af-11ea-9ad3-3bcfc330e42a`,
|
||||
globalState: {
|
||||
time: {
|
||||
from: 'now-30m',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
'/app/ml/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Anomaly Explorer Page', () => {
|
||||
it('should generate valid URL for the Anomaly Explorer page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_EXPLORER,
|
||||
pageState: {
|
||||
jobIds: ['fq_single_1'],
|
||||
mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 },
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 0,
|
||||
},
|
||||
timeRange: {
|
||||
from: '2019-02-07T00:00:00.000Z',
|
||||
to: '2020-08-13T17:15:00.000Z',
|
||||
mode: 'absolute',
|
||||
},
|
||||
query: {
|
||||
analyze_wildcard: true,
|
||||
query: '*',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
"/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*'))"
|
||||
);
|
||||
});
|
||||
it('should generate valid URL for the Anomaly Explorer page for multiple jobIds', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.ANOMALY_EXPLORER,
|
||||
pageState: {
|
||||
jobIds: ['fq_single_1', 'logs_categorization_1'],
|
||||
timeRange: {
|
||||
from: '2019-02-07T00:00:00.000Z',
|
||||
to: '2020-08-13T17:15:00.000Z',
|
||||
mode: 'absolute',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
"/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:())"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single Metric Viewer Page', () => {
|
||||
it('should generate valid URL for the Single Metric Viewer page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.SINGLE_METRIC_VIEWER,
|
||||
pageState: {
|
||||
jobIds: ['logs_categorization_1'],
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 0,
|
||||
},
|
||||
timeRange: {
|
||||
from: '2020-07-12T00:39:02.912Z',
|
||||
to: '2020-07-22T15:52:18.613Z',
|
||||
mode: 'absolute',
|
||||
},
|
||||
query: {
|
||||
analyze_wildcard: true,
|
||||
query: '*',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*')))"
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Single Metric Viewer page with extra settings', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.SINGLE_METRIC_VIEWER,
|
||||
pageState: {
|
||||
jobIds: ['logs_categorization_1'],
|
||||
detectorIndex: 0,
|
||||
entities: { mlcategory: '2' },
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 0,
|
||||
},
|
||||
timeRange: {
|
||||
from: '2020-07-12T00:39:02.912Z',
|
||||
to: '2020-07-22T15:52:18.613Z',
|
||||
mode: 'absolute',
|
||||
},
|
||||
zoom: {
|
||||
from: '2020-07-20T23:58:29.367Z',
|
||||
to: '2020-07-21T11:00:13.173Z',
|
||||
},
|
||||
query: {
|
||||
analyze_wildcard: true,
|
||||
query: '*',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2')),query:(query_string:(analyze_wildcard:!t,query:'*')),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z'))"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DataFrameAnalytics', () => {
|
||||
describe('JobManagement Page', () => {
|
||||
it('should generate valid URL for the Data Frame Analytics job management page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
|
||||
});
|
||||
expect(url).toBe('/app/ml/data_frame_analytics');
|
||||
});
|
||||
it('should generate valid URL for the Data Frame Analytics job management page with jobId', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
|
||||
pageState: {
|
||||
jobId: 'grid_regression_1',
|
||||
},
|
||||
});
|
||||
expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(jobId:grid_regression_1)');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Data Frame Analytics job management page with groupIds', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
|
||||
pageState: {
|
||||
groupIds: ['group_1', 'group_2'],
|
||||
},
|
||||
});
|
||||
expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(groupIds:!(group_1,group_2))');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExplorationPage', () => {
|
||||
it('should generate valid URL for the Data Frame Analytics exploration page for job', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
|
||||
pageState: {
|
||||
jobId: 'grid_regression_1',
|
||||
analysisType: ANALYSIS_CONFIG_TYPE.REGRESSION,
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
'/app/ml/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DataVisualizer', () => {
|
||||
it('should generate valid URL for the Data Visualizer page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER,
|
||||
});
|
||||
expect(url).toBe('/app/ml/datavisualizer');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the File Data Visualizer import page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER_FILE,
|
||||
});
|
||||
expect(url).toBe('/app/ml/filedatavisualizer');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Index Data Visualizer select index pattern or saved search page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
|
||||
});
|
||||
expect(url).toBe('/app/ml/datavisualizer_index_select');
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Index Data Visualizer Viewer page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
|
||||
pageState: {
|
||||
index: '3da93760-e0af-11ea-9ad3-3bcfc330e42a',
|
||||
globalState: {
|
||||
time: {
|
||||
from: 'now-30m',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(url).toBe(
|
||||
'/app/ml/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error in case the page is not provided', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
// @ts-ignore
|
||||
await urlGenerator.createUrl({ jobIds: ['test-job'] }).catch((e) => {
|
||||
expect(e.message).toEqual('Page type is not provided or unknown');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { CoreSetup } from 'kibana/public';
|
||||
import {
|
||||
SharePluginSetup,
|
||||
UrlGeneratorsDefinition,
|
||||
UrlGeneratorState,
|
||||
} from '../../../../../src/plugins/share/public';
|
||||
import { MlStartDependencies } from '../plugin';
|
||||
import { ML_PAGES, ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator';
|
||||
import { MlUrlGeneratorState } from '../../common/types/ml_url_generator';
|
||||
import {
|
||||
createAnomalyDetectionJobManagementUrl,
|
||||
createAnomalyDetectionCreateJobSelectType,
|
||||
createExplorerUrl,
|
||||
createSingleMetricViewerUrl,
|
||||
} from './anomaly_detection_urls_generator';
|
||||
import {
|
||||
createDataFrameAnalyticsJobManagementUrl,
|
||||
createDataFrameAnalyticsExplorationUrl,
|
||||
} from './data_frame_analytics_urls_generator';
|
||||
import {
|
||||
createIndexDataVisualizerUrl,
|
||||
createDataVisualizerUrl,
|
||||
} from './data_visualizer_urls_generator';
|
||||
|
||||
declare module '../../../../../src/plugins/share/public' {
|
||||
export interface UrlGeneratorStateMapping {
|
||||
[ML_APP_URL_GENERATOR]: UrlGeneratorState<MlUrlGeneratorState>;
|
||||
}
|
||||
}
|
||||
|
||||
interface Params {
|
||||
appBasePath: string;
|
||||
useHash: boolean;
|
||||
}
|
||||
|
||||
export class MlUrlGenerator implements UrlGeneratorsDefinition<typeof ML_APP_URL_GENERATOR> {
|
||||
constructor(private readonly params: Params) {}
|
||||
|
||||
public readonly id = ML_APP_URL_GENERATOR;
|
||||
|
||||
public readonly createUrl = async (mlUrlGeneratorState: MlUrlGeneratorState): Promise<string> => {
|
||||
const appBasePath = this.params.appBasePath;
|
||||
switch (mlUrlGeneratorState.page) {
|
||||
case ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE:
|
||||
return createAnomalyDetectionJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
case ML_PAGES.ANOMALY_EXPLORER:
|
||||
return createExplorerUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE:
|
||||
return createAnomalyDetectionCreateJobSelectType(
|
||||
appBasePath,
|
||||
mlUrlGeneratorState.pageState
|
||||
);
|
||||
case ML_PAGES.SINGLE_METRIC_VIEWER:
|
||||
return createSingleMetricViewerUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
case ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE:
|
||||
return createDataFrameAnalyticsJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
case ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION:
|
||||
return createDataFrameAnalyticsExplorationUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
case ML_PAGES.DATA_VISUALIZER:
|
||||
case ML_PAGES.DATA_VISUALIZER_FILE:
|
||||
case ML_PAGES.DATA_VISUALIZER_INDEX_SELECT:
|
||||
return createDataVisualizerUrl(appBasePath, mlUrlGeneratorState);
|
||||
case ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER:
|
||||
return createIndexDataVisualizerUrl(appBasePath, mlUrlGeneratorState.pageState);
|
||||
default:
|
||||
throw new Error('Page type is not provided or unknown');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the URL generator
|
||||
*/
|
||||
export function registerUrlGenerator(
|
||||
share: SharePluginSetup,
|
||||
core: CoreSetup<MlStartDependencies>
|
||||
) {
|
||||
const baseUrl = core.http.basePath.prepend('/app/ml');
|
||||
share.urlGenerators.registerUrlGenerator(
|
||||
new MlUrlGenerator({
|
||||
appBasePath: baseUrl,
|
||||
useHash: core.uiSettings.get('state:storeInSessionStorage'),
|
||||
})
|
||||
);
|
||||
}
|
|
@ -34,7 +34,7 @@ import { registerFeature } from './register_feature';
|
|||
import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
|
||||
import { registerMlUiActions } from './ui_actions';
|
||||
import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public';
|
||||
import { registerUrlGenerator } from './url_generator';
|
||||
import { registerUrlGenerator } from './ml_url_generator';
|
||||
import { isFullLicense, isMlEnabled } from '../common/license';
|
||||
import { registerEmbeddables } from './embeddables';
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionContextMapping, createAction } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { MlCoreSetup } from '../plugin';
|
||||
import { ML_APP_URL_GENERATOR } from '../url_generator';
|
||||
import { ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator';
|
||||
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, SwimLaneDrilldownContext } from '../embeddables';
|
||||
|
||||
export const OPEN_IN_ANOMALY_EXPLORER_ACTION = 'openInAnomalyExplorerAction';
|
||||
|
@ -32,19 +32,21 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
|
|||
|
||||
return urlGenerator.createUrl({
|
||||
page: 'explorer',
|
||||
jobIds,
|
||||
timeRange,
|
||||
mlExplorerSwimlane: {
|
||||
viewByFromPage: fromPage,
|
||||
viewByPerPage: perPage,
|
||||
viewByFieldName: viewBy,
|
||||
...(data
|
||||
? {
|
||||
selectedType: data.type,
|
||||
selectedTimes: data.times,
|
||||
selectedLanes: data.lanes,
|
||||
}
|
||||
: {}),
|
||||
pageState: {
|
||||
jobIds,
|
||||
timeRange,
|
||||
mlExplorerSwimlane: {
|
||||
viewByFromPage: fromPage,
|
||||
viewByPerPage: perPage,
|
||||
viewByFieldName: viewBy,
|
||||
...(data
|
||||
? {
|
||||
selectedType: data.type,
|
||||
selectedTimes: data.times,
|
||||
selectedLanes: data.lanes,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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 { MlUrlGenerator } from './url_generator';
|
||||
|
||||
describe('MlUrlGenerator', () => {
|
||||
const urlGenerator = new MlUrlGenerator({
|
||||
appBasePath: '/app/ml',
|
||||
useHash: false,
|
||||
});
|
||||
|
||||
it('should generate valid URL for the Anomaly Explorer page', async () => {
|
||||
const url = await urlGenerator.createUrl({
|
||||
page: 'explorer',
|
||||
jobIds: ['test-job'],
|
||||
mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 },
|
||||
});
|
||||
expect(url).toBe(
|
||||
'/app/ml/explorer#?_g=(ml:(jobIds:!(test-job)))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20))'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error in case the page is not provided', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
// @ts-ignore
|
||||
await urlGenerator.createUrl({ jobIds: ['test-job'] }).catch((e) => {
|
||||
expect(e.message).toEqual('Page type is not provided or unknown');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* 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 { CoreSetup } from 'kibana/public';
|
||||
import {
|
||||
SharePluginSetup,
|
||||
UrlGeneratorsDefinition,
|
||||
UrlGeneratorState,
|
||||
} from '../../../../src/plugins/share/public';
|
||||
import { TimeRange } from '../../../../src/plugins/data/public';
|
||||
import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { JobId } from '../../reporting/common/types';
|
||||
import { ExplorerAppState } from './application/explorer/explorer_dashboard_service';
|
||||
import { MlStartDependencies } from './plugin';
|
||||
|
||||
declare module '../../../../src/plugins/share/public' {
|
||||
export interface UrlGeneratorStateMapping {
|
||||
[ML_APP_URL_GENERATOR]: UrlGeneratorState<MlUrlGeneratorState>;
|
||||
}
|
||||
}
|
||||
|
||||
export const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR';
|
||||
|
||||
export interface ExplorerUrlState {
|
||||
/**
|
||||
* ML App Page
|
||||
*/
|
||||
page: 'explorer';
|
||||
/**
|
||||
* Job IDs
|
||||
*/
|
||||
jobIds: JobId[];
|
||||
/**
|
||||
* Optionally set the time range in the time picker.
|
||||
*/
|
||||
timeRange?: TimeRange;
|
||||
/**
|
||||
* Optional state for the swim lane
|
||||
*/
|
||||
mlExplorerSwimlane?: ExplorerAppState['mlExplorerSwimlane'];
|
||||
mlExplorerFilter?: ExplorerAppState['mlExplorerFilter'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of ML URL state based on page
|
||||
*/
|
||||
export type MlUrlGeneratorState = ExplorerUrlState;
|
||||
|
||||
export interface ExplorerQueryState {
|
||||
ml: { jobIds: JobId[] };
|
||||
time?: TimeRange;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
appBasePath: string;
|
||||
useHash: boolean;
|
||||
}
|
||||
|
||||
export class MlUrlGenerator implements UrlGeneratorsDefinition<typeof ML_APP_URL_GENERATOR> {
|
||||
constructor(private readonly params: Params) {}
|
||||
|
||||
public readonly id = ML_APP_URL_GENERATOR;
|
||||
|
||||
public readonly createUrl = async ({ page, ...params }: MlUrlGeneratorState): Promise<string> => {
|
||||
if (page === 'explorer') {
|
||||
return this.createExplorerUrl(params);
|
||||
}
|
||||
throw new Error('Page type is not provided or unknown');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates URL to the Anomaly Explorer page
|
||||
*/
|
||||
private createExplorerUrl({
|
||||
timeRange,
|
||||
jobIds,
|
||||
mlExplorerSwimlane = {},
|
||||
mlExplorerFilter = {},
|
||||
}: Omit<ExplorerUrlState, 'page'>): string {
|
||||
const appState: ExplorerAppState = {
|
||||
mlExplorerSwimlane,
|
||||
mlExplorerFilter,
|
||||
};
|
||||
|
||||
const queryState: ExplorerQueryState = {
|
||||
ml: {
|
||||
jobIds,
|
||||
},
|
||||
};
|
||||
|
||||
if (timeRange) queryState.time = timeRange;
|
||||
|
||||
let url = `${this.params.appBasePath}/explorer`;
|
||||
url = setStateToKbnUrl<ExplorerQueryState>('_g', queryState, { useHash: false }, url);
|
||||
url = setStateToKbnUrl('_a', appState, { useHash: false }, url);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the URL generator
|
||||
*/
|
||||
export function registerUrlGenerator(
|
||||
share: SharePluginSetup,
|
||||
core: CoreSetup<MlStartDependencies>
|
||||
) {
|
||||
const baseUrl = core.http.basePath.prepend('/app/ml');
|
||||
share.urlGenerators.registerUrlGenerator(
|
||||
new MlUrlGenerator({
|
||||
appBasePath: baseUrl,
|
||||
useHash: core.uiSettings.get('state:storeInSessionStorage'),
|
||||
})
|
||||
);
|
||||
}
|
|
@ -18,7 +18,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup)
|
|||
addAppLinksToSampleDataset('ecommerce', [
|
||||
{
|
||||
path:
|
||||
'/app/ml#/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
'/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
label: sampleDataLinkLabel,
|
||||
icon: 'machineLearningApp',
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup)
|
|||
addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path:
|
||||
'/app/ml#/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
'/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
label: sampleDataLinkLabel,
|
||||
icon: 'machineLearningApp',
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue