From d88b3a6ddea2e3016d95cadec98be9426776f124 Mon Sep 17 00:00:00 2001
From: Quynh Nguyen <43350163+qn895@users.noreply.github.com>
Date: Thu, 17 Sep 2020 12:09:08 -0500
Subject: [PATCH] [ML] Migrate internal urls to non-hash paths (#76735)
Co-authored-by: Elastic Machine
---
x-pack/plugins/ml/common/constants/app.ts | 5 +
.../common/constants/data_frame_analytics.ts | 5 +
.../ml/common/constants/ml_url_generator.ts | 8 +
.../ml/common/types/data_frame_analytics.ts | 7 +-
.../ml/common/types/ml_url_generator.ts | 82 ++-
.../plugins/ml/common/util/analytics_utils.ts | 2 +-
x-pack/plugins/ml/kibana.json | 3 +-
x-pack/plugins/ml/public/application/app.tsx | 11 +
.../capabilities/check_capabilities.ts | 38 +-
.../annotations_table.test.js.snap | 677 +++++++++++++-----
.../annotations_table/annotations_table.js | 84 ++-
.../components/anomalies_table/links_menu.js | 57 +-
.../anomaly_results_view_selector.test.tsx | 34 +-
.../anomaly_results_view_selector.tsx | 25 +-
.../custom_hooks/use_create_ad_links.ts | 7 +-
.../components/data_grid/data_grid.tsx | 3 +-
.../decision_path_popover.tsx | 3 +-
.../data_recognizer/recognized_result.js | 9 +-
.../components/navigation_menu/main_tabs.tsx | 118 ++-
.../components/rule_editor/scope_section.js | 13 +-
.../application/contexts/kibana/index.ts | 2 +-
.../contexts/kibana/kibana_context.ts | 2 +-
.../contexts/kibana/use_create_url.ts | 59 ++
.../data_frame_analytics/common/analytics.ts | 7 +-
.../data_frame_analytics/common/index.ts | 2 +-
.../configuration_step/job_type.tsx | 2 +-
.../supported_fields_message.tsx | 5 +-
.../create_step_footer/create_step_footer.tsx | 4 +-
.../view_results_panel/view_results_panel.tsx | 4 +-
.../exploration_results_table.tsx | 4 +-
.../pages/analytics_exploration/page.tsx | 5 +-
.../action_view/use_view_action.tsx | 24 +-
.../analytics_list/analytics_list.tsx | 8 +-
.../components/analytics_list/common.ts | 16 +-
.../components/analytics_list/use_columns.tsx | 18 +-
.../models_management/models_list.tsx | 26 +-
.../use_create_analytics_form/reducer.ts | 2 +-
.../hooks/use_create_analytics_form/state.ts | 9 +-
.../analytics_service/get_analytics.ts | 5 +-
.../datavisualizer_selector.tsx | 10 +-
.../results_links/results_links.tsx | 88 ++-
.../actions_panel/actions_panel.tsx | 38 +-
.../explorer_no_jobs_found.test.js.snap | 23 +-
.../explorer_no_jobs_found.js | 51 +-
.../explorer_no_jobs_found.test.js | 3 +
.../explorer_charts_container.js | 44 +-
.../explorer_charts_container.test.js | 26 +-
.../components/job_actions/results.js | 73 +-
.../job_details/extract_job_details.js | 4 +-
.../forecasts_table/forecasts_table.js | 93 ++-
.../components/jobs_list/job_description.js | 8 +-
.../components/jobs_list/job_id_link.tsx | 63 ++
.../components/jobs_list/jobs_list.js | 8 +-
.../new_job_button/new_job_button.js | 8 +-
.../jobs/jobs_list/components/utils.js | 2 +-
.../calendars/calendars_selection.tsx | 3 +-
.../pages/components/summary_step/summary.tsx | 7 +-
.../preconfigured_job_redirect.ts | 9 +-
.../jobs/new_job/pages/job_type/page.tsx | 8 +-
.../jobs/new_job/recognize/page.tsx | 30 +-
.../jobs/new_job/recognize/resolvers.ts | 24 +-
.../jobs_list_page/jobs_list_page.tsx | 6 +-
.../application/management/jobs_list/index.ts | 10 +-
.../ml_nodes_check/check_ml_nodes.ts | 4 +-
.../components/analytics_panel/actions.tsx | 58 +-
.../analytics_panel/analytics_panel.tsx | 18 +-
.../anomaly_detection_panel/actions.tsx | 28 +-
.../anomaly_detection_panel.tsx | 28 +-
.../anomaly_detection_panel/table.tsx | 3 +-
.../public/application/routing/breadcrumbs.ts | 22 +-
.../public/application/routing/resolvers.ts | 8 +-
.../ml/public/application/routing/router.tsx | 11 +-
.../analytics_job_creation.tsx | 7 +-
.../analytics_job_exploration.tsx | 35 +-
.../analytics_jobs_list.tsx | 9 +-
.../data_frame_analytics/models_list.tsx | 9 +-
.../routes/datavisualizer/datavisualizer.tsx | 14 +-
.../routes/datavisualizer/file_based.tsx | 14 +-
.../routes/datavisualizer/index_based.tsx | 20 +-
.../application/routing/routes/explorer.tsx | 9 +-
.../application/routing/routes/jobs_list.tsx | 6 +-
.../routes/new_job/index_or_search.tsx | 42 +-
.../routing/routes/new_job/job_type.tsx | 6 +-
.../routing/routes/new_job/recognize.tsx | 20 +-
.../routing/routes/new_job/wizard.tsx | 71 +-
.../application/routing/routes/overview.tsx | 13 +-
.../routing/routes/settings/calendar_list.tsx | 23 +-
.../routes/settings/calendar_new_edit.tsx | 30 +-
.../routing/routes/settings/filter_list.tsx | 23 +-
.../routes/settings/filter_list_new_edit.tsx | 31 +-
.../routing/routes/settings/settings.tsx | 13 +-
.../routes/timeseriesexplorer.test.tsx | 5 +
.../routing/routes/timeseriesexplorer.tsx | 9 +-
.../application/routing/use_resolver.ts | 7 +-
.../application/services/job_service.js | 1 -
.../settings/anomaly_detection_settings.tsx | 16 +-
.../__snapshots__/calendar_form.test.js.snap | 64 +-
.../edit/calendar_form/calendar_form.js | 5 +-
.../edit/calendar_form/calendar_form.test.js | 4 +
.../settings/calendars/edit/new_calendar.js | 15 +-
.../calendars/edit/new_calendar.test.js | 4 +
.../table/__snapshots__/table.test.js.snap | 1 -
.../settings/calendars/list/table/table.js | 18 +-
.../calendars/list/table/table.test.js | 23 +-
.../filter_lists/edit/edit_filter_list.js | 19 +-
.../settings/filter_lists/list/table.js | 17 +-
.../application/settings/settings.test.tsx | 4 +
.../timeseriesexplorer_no_jobs_found.tsx | 58 +-
.../ml/public/application/util/chart_utils.js | 62 +-
.../application/util/chart_utils.test.js | 15 -
.../application/util/get_selected_ids_url.ts | 39 -
.../application/util/recently_accessed.ts | 2 +-
.../anomaly_detection_urls_generator.ts | 125 ++--
.../ml/public/ml_url_generator/common.ts | 51 +-
.../data_frame_analytics_urls_generator.ts | 37 +-
.../data_visualizer_urls_generator.ts | 29 -
.../ml_url_generator/ml_url_generator.test.ts | 2 +-
.../ml_url_generator/ml_url_generator.ts | 42 +-
.../settings_urls_generator.tsx | 45 ++
x-pack/plugins/ml/public/register_feature.ts | 2 +-
.../services/ml/settings_filter_list.ts | 2 +-
121 files changed, 2294 insertions(+), 1038 deletions(-)
create mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx
delete mode 100644 x-pack/plugins/ml/public/application/util/get_selected_ids_url.ts
delete mode 100644 x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts
create mode 100644 x-pack/plugins/ml/public/ml_url_generator/settings_urls_generator.tsx
diff --git a/x-pack/plugins/ml/common/constants/app.ts b/x-pack/plugins/ml/common/constants/app.ts
index 97dd7a7b0fef..3d54e9e150fe 100644
--- a/x-pack/plugins/ml/common/constants/app.ts
+++ b/x-pack/plugins/ml/common/constants/app.ts
@@ -4,6 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { i18n } from '@kbn/i18n';
+
export const PLUGIN_ID = 'ml';
export const PLUGIN_ICON = 'machineLearningApp';
export const PLUGIN_ICON_SOLUTION = 'logoKibana';
+export const ML_APP_NAME = i18n.translate('xpack.ml.navMenu.mlAppNameText', {
+ defaultMessage: 'Machine Learning',
+});
diff --git a/x-pack/plugins/ml/common/constants/data_frame_analytics.ts b/x-pack/plugins/ml/common/constants/data_frame_analytics.ts
index 830537cbadbc..9a7af2496c03 100644
--- a/x-pack/plugins/ml/common/constants/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/common/constants/data_frame_analytics.ts
@@ -4,4 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+export const ANALYSIS_CONFIG_TYPE = {
+ OUTLIER_DETECTION: 'outlier_detection',
+ REGRESSION: 'regression',
+ CLASSIFICATION: 'classification',
+} as const;
export const DEFAULT_RESULTS_FIELD = 'ml';
diff --git a/x-pack/plugins/ml/common/constants/ml_url_generator.ts b/x-pack/plugins/ml/common/constants/ml_url_generator.ts
index 44f33aa329e7..541b8af6fc0f 100644
--- a/x-pack/plugins/ml/common/constants/ml_url_generator.ts
+++ b/x-pack/plugins/ml/common/constants/ml_url_generator.ts
@@ -31,8 +31,16 @@ export const ML_PAGES = {
* Open index data visualizer viewer page
*/
DATA_VISUALIZER_INDEX_VIEWER: 'jobs/new_job/datavisualizer',
+ ANOMALY_DETECTION_CREATE_JOB: `jobs/new_job`,
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: `jobs/new_job/step/job_type`,
+ ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: `jobs/new_job/step/index_or_search`,
SETTINGS: 'settings',
CALENDARS_MANAGE: 'settings/calendars_list',
+ CALENDARS_NEW: 'settings/calendars_list/new_calendar',
+ CALENDARS_EDIT: 'settings/calendars_list/edit_calendar',
FILTER_LISTS_MANAGE: 'settings/filter_lists',
+ FILTER_LISTS_NEW: 'settings/filter_lists/new_filter_list',
+ FILTER_LISTS_EDIT: 'settings/filter_lists/edit_filter_list',
+ ACCESS_DENIED: 'access-denied',
+ OVERVIEW: 'overview',
} as const;
diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
index 96d6c81a3d30..5d0ecf96fb6b 100644
--- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts
@@ -6,6 +6,7 @@
import Boom from 'boom';
import { EsErrorBody } from '../util/errors';
+import { ANALYSIS_CONFIG_TYPE } from '../constants/data_frame_analytics';
export interface DeleteDataFrameAnalyticsWithIndexStatus {
success: boolean;
@@ -81,8 +82,4 @@ export interface DataFrameAnalyticsConfig {
allow_lazy_start?: boolean;
}
-export enum ANALYSIS_CONFIG_TYPE {
- OUTLIER_DETECTION = 'outlier_detection',
- REGRESSION = 'regression',
- CLASSIFICATION = 'classification',
-}
+export type DataFrameAnalysisConfigType = typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE];
diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts
index 234be8b6faf9..d176c22bdbb6 100644
--- a/x-pack/plugins/ml/common/types/ml_url_generator.ts
+++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts
@@ -5,27 +5,21 @@
*/
import { RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common/query';
-import { JobId } from '../../../reporting/common/types';
+import { JobId } from './anomaly_detection_jobs/job';
import { ML_PAGES } from '../constants/ml_url_generator';
+import { DataFrameAnalysisConfigType } from './data_frame_analytics';
type OptionalPageState = object | undefined;
export type MLPageState = PageState extends OptionalPageState
- ? { page: PageType; pageState?: PageState }
+ ? { page: PageType; pageState?: PageState; excludeBasePath?: boolean }
: 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];
+ ? { page: PageType; pageState: PageState; excludeBasePath?: boolean }
+ : { page: PageType; excludeBasePath?: boolean };
export interface MlCommonGlobalState {
time?: TimeRange;
+ refreshInterval?: RefreshInterval;
}
export interface MlCommonAppState {
[key: string]: any;
@@ -42,16 +36,28 @@ export interface MlGenericUrlPageState extends MlIndexBasedSearchState {
[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 type MlGenericUrlState = MLPageState<
+ | typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
+ | typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB
+ | typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE
+ | typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
+ | typeof ML_PAGES.OVERVIEW
+ | typeof ML_PAGES.CALENDARS_MANAGE
+ | typeof ML_PAGES.CALENDARS_NEW
+ | typeof ML_PAGES.FILTER_LISTS_MANAGE
+ | typeof ML_PAGES.FILTER_LISTS_NEW
+ | typeof ML_PAGES.SETTINGS
+ | typeof ML_PAGES.ACCESS_DENIED
+ | typeof ML_PAGES.DATA_VISUALIZER
+ | typeof ML_PAGES.DATA_VISUALIZER_FILE
+ | typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
+ MlGenericUrlPageState | undefined
+>;
export interface AnomalyDetectionQueryState {
jobId?: JobId;
groupIds?: string[];
+ globalState?: MlCommonGlobalState;
}
export type AnomalyDetectionUrlState = MLPageState<
@@ -86,7 +92,7 @@ export interface ExplorerUrlPageState {
/**
* Job IDs
*/
- jobIds: JobId[];
+ jobIds?: JobId[];
/**
* Optionally set the time range in the time picker.
*/
@@ -104,6 +110,7 @@ export interface ExplorerUrlPageState {
*/
mlExplorerSwimlane?: ExplorerAppState['mlExplorerSwimlane'];
mlExplorerFilter?: ExplorerAppState['mlExplorerFilter'];
+ globalState?: MlCommonGlobalState;
}
export type ExplorerUrlState = MLPageState;
@@ -122,6 +129,7 @@ export interface TimeSeriesExplorerAppState {
to?: string;
};
mlTimeSeriesExplorer?: {
+ forecastId?: string;
detectorIndex?: number;
entities?: Record;
};
@@ -131,10 +139,12 @@ export interface TimeSeriesExplorerAppState {
export interface TimeSeriesExplorerPageState
extends Pick,
Pick {
- jobIds: JobId[];
+ jobIds?: JobId[];
timeRange?: TimeRange;
detectorIndex?: number;
entities?: Record;
+ forecastId?: string;
+ globalState?: MlCommonGlobalState;
}
export type TimeSeriesExplorerUrlState = MLPageState<
@@ -145,6 +155,7 @@ export type TimeSeriesExplorerUrlState = MLPageState<
export interface DataFrameAnalyticsQueryState {
jobId?: JobId | JobId[];
groupIds?: string[];
+ globalState?: MlCommonGlobalState;
}
export type DataFrameAnalyticsUrlState = MLPageState<
@@ -152,17 +163,10 @@ export type DataFrameAnalyticsUrlState = MLPageState<
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;
+ analysisType: DataFrameAnalysisConfigType;
};
}
@@ -170,7 +174,24 @@ export type DataFrameAnalyticsExplorationUrlState = MLPageState<
typeof ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
{
jobId: JobId;
- analysisType: DataFrameAnalyticsType;
+ analysisType: DataFrameAnalysisConfigType;
+ globalState?: MlCommonGlobalState;
+ }
+>;
+
+export type CalendarEditUrlState = MLPageState<
+ typeof ML_PAGES.CALENDARS_EDIT,
+ {
+ calendarId: string;
+ globalState?: MlCommonGlobalState;
+ }
+>;
+
+export type FilterEditUrlState = MLPageState<
+ typeof ML_PAGES.FILTER_LISTS_EDIT,
+ {
+ filterId: string;
+ globalState?: MlCommonGlobalState;
}
>;
@@ -183,5 +204,6 @@ export type MlUrlGeneratorState =
| TimeSeriesExplorerUrlState
| DataFrameAnalyticsUrlState
| DataFrameAnalyticsExplorationUrlState
- | DataVisualizerUrlState
+ | CalendarEditUrlState
+ | FilterEditUrlState
| MlGenericUrlState;
diff --git a/x-pack/plugins/ml/common/util/analytics_utils.ts b/x-pack/plugins/ml/common/util/analytics_utils.ts
index d725984a47d6..d231ed434438 100644
--- a/x-pack/plugins/ml/common/util/analytics_utils.ts
+++ b/x-pack/plugins/ml/common/util/analytics_utils.ts
@@ -9,8 +9,8 @@ import {
ClassificationAnalysis,
OutlierAnalysis,
RegressionAnalysis,
- ANALYSIS_CONFIG_TYPE,
} from '../types/data_frame_analytics';
+import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics';
export const isOutlierAnalysis = (arg: any): arg is OutlierAnalysis => {
const keys = Object.keys(arg);
diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json
index fc673397ef17..2c5dbe108ab1 100644
--- a/x-pack/plugins/ml/kibana.json
+++ b/x-pack/plugins/ml/kibana.json
@@ -16,7 +16,8 @@
"embeddable",
"uiActions",
"kibanaLegacy",
- "indexPatternManagement"
+ "indexPatternManagement",
+ "discover"
],
"optionalPlugins": [
"home",
diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx
index c281dc4e9ae0..e3bcc53fe697 100644
--- a/x-pack/plugins/ml/public/application/app.tsx
+++ b/x-pack/plugins/ml/public/application/app.tsx
@@ -20,6 +20,7 @@ import { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { MlRouter } from './routing';
import { mlApiServicesProvider } from './services/ml_api_service';
import { HttpService } from './services/http_service';
+import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../common/constants/ml_url_generator';
export type MlDependencies = Omit &
MlStartDependencies;
@@ -50,11 +51,21 @@ export interface MlServicesContext {
export type MlGlobalServices = ReturnType;
const App: FC = ({ coreStart, deps, appMountParams }) => {
+ const redirectToMlAccessDeniedPage = async () => {
+ const accessDeniedPageUrl = await deps.share.urlGenerators
+ .getUrlGenerator(ML_APP_URL_GENERATOR)
+ .createUrl({
+ page: ML_PAGES.ACCESS_DENIED,
+ });
+ await coreStart.application.navigateToUrl(accessDeniedPageUrl);
+ };
+
const pageDeps = {
history: appMountParams.history,
indexPatterns: deps.data.indexPatterns,
config: coreStart.uiSettings!,
setBreadcrumbs: coreStart.chrome!.setBreadcrumbs,
+ redirectToMlAccessDeniedPage,
};
const services = {
appName: 'ML',
diff --git a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts
index 653eca126006..cdd25821ea5c 100644
--- a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts
+++ b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts
@@ -33,10 +33,12 @@ export function checkGetManagementMlJobsResolver() {
});
}
-export function checkGetJobsCapabilitiesResolver(): Promise {
+export function checkGetJobsCapabilitiesResolver(
+ redirectToMlAccessDeniedPage: () => Promise
+): Promise {
return new Promise((resolve, reject) => {
getCapabilities()
- .then(({ capabilities, isPlatinumOrTrialLicense }) => {
+ .then(async ({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
// the minimum privilege for using ML with a platinum or trial license is being able to get the transforms list.
// all other functionality is controlled by the return capabilities object.
@@ -46,21 +48,23 @@ export function checkGetJobsCapabilitiesResolver(): Promise {
if (_capabilities.canGetJobs || isPlatinumOrTrialLicense === false) {
return resolve(_capabilities);
} else {
- window.location.href = '#/access-denied';
+ await redirectToMlAccessDeniedPage();
return reject();
}
})
- .catch((e) => {
- window.location.href = '#/access-denied';
+ .catch(async (e) => {
+ await redirectToMlAccessDeniedPage();
return reject();
});
});
}
-export function checkCreateJobsCapabilitiesResolver(): Promise {
+export function checkCreateJobsCapabilitiesResolver(
+ redirectToJobsManagementPage: () => Promise
+): Promise {
return new Promise((resolve, reject) => {
getCapabilities()
- .then(({ capabilities, isPlatinumOrTrialLicense }) => {
+ .then(async ({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
// if the license is basic (isPlatinumOrTrialLicense === false) then do not redirect,
// allow the promise to resolve as the separate license check will redirect then user to
@@ -69,34 +73,36 @@ export function checkCreateJobsCapabilitiesResolver(): Promise {
return resolve(_capabilities);
} else {
// if the user has no permission to create a job,
- // redirect them back to the Transforms Management page
- window.location.href = '#/jobs';
+ // redirect them back to the Anomaly Detection Management page
+ await redirectToJobsManagementPage();
return reject();
}
})
- .catch((e) => {
- window.location.href = '#/jobs';
+ .catch(async (e) => {
+ await redirectToJobsManagementPage();
return reject();
});
});
}
-export function checkFindFileStructurePrivilegeResolver(): Promise {
+export function checkFindFileStructurePrivilegeResolver(
+ redirectToMlAccessDeniedPage: () => Promise
+): Promise {
return new Promise((resolve, reject) => {
getCapabilities()
- .then(({ capabilities }) => {
+ .then(async ({ capabilities }) => {
_capabilities = capabilities;
// the minimum privilege for using ML with a basic license is being able to use the datavisualizer.
// all other functionality is controlled by the return _capabilities object
if (_capabilities.canFindFileStructure) {
return resolve(_capabilities);
} else {
- window.location.href = '#/access-denied';
+ await redirectToMlAccessDeniedPage();
return reject();
}
})
- .catch((e) => {
- window.location.href = '#/access-denied';
+ .catch(async (e) => {
+ await redirectToMlAccessDeniedPage();
return reject();
});
});
diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap
index 9eb44c71aa79..114a6b235d1a 100644
--- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap
+++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap
@@ -1,170 +1,527 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AnnotationsTable Initialization with annotations prop. 1`] = `
-
- ",
- "end_timestamp": 1455041968976,
- "job_id": "farequote",
- "modified_time": 1546417097181,
- "modified_username": "",
- "timestamp": 1455026177994,
- "type": "annotation",
- },
- ]
- }
- pagination={
+",
+ "end_timestamp": 1455041968976,
+ "job_id": "farequote",
+ "modified_time": 1546417097181,
+ "modified_username": "",
+ "timestamp": 1455026177994,
+ "type": "annotation",
+ },
+ ]
+ }
+ intl={
+ Object {
+ "defaultFormats": Object {},
+ "defaultLocale": "en",
+ "formatDate": [Function],
+ "formatHTMLMessage": [Function],
+ "formatMessage": [Function],
+ "formatNumber": [Function],
+ "formatPlural": [Function],
+ "formatRelative": [Function],
+ "formatTime": [Function],
+ "formats": Object {
+ "date": Object {
+ "full": Object {
+ "day": "numeric",
+ "month": "long",
+ "weekday": "long",
+ "year": "numeric",
+ },
+ "long": Object {
+ "day": "numeric",
+ "month": "long",
+ "year": "numeric",
+ },
+ "medium": Object {
+ "day": "numeric",
+ "month": "short",
+ "year": "numeric",
+ },
+ "short": Object {
+ "day": "numeric",
+ "month": "numeric",
+ "year": "2-digit",
},
- ],
- }
- }
- sorting={
- Object {
- "sort": Object {
- "direction": "asc",
- "field": "timestamp",
},
- }
+ "number": Object {
+ "currency": Object {
+ "style": "currency",
+ },
+ "percent": Object {
+ "style": "percent",
+ },
+ },
+ "relative": Object {
+ "days": Object {
+ "units": "day",
+ },
+ "hours": Object {
+ "units": "hour",
+ },
+ "minutes": Object {
+ "units": "minute",
+ },
+ "months": Object {
+ "units": "month",
+ },
+ "seconds": Object {
+ "units": "second",
+ },
+ "years": Object {
+ "units": "year",
+ },
+ },
+ "time": Object {
+ "full": Object {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short",
+ },
+ "long": Object {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ "timeZoneName": "short",
+ },
+ "medium": Object {
+ "hour": "numeric",
+ "minute": "numeric",
+ "second": "numeric",
+ },
+ "short": Object {
+ "hour": "numeric",
+ "minute": "numeric",
+ },
+ },
+ },
+ "formatters": Object {
+ "getDateTimeFormat": [Function],
+ "getMessageFormat": [Function],
+ "getNumberFormat": [Function],
+ "getPluralFormat": [Function],
+ "getRelativeFormat": [Function],
+ },
+ "locale": "en",
+ "messages": Object {},
+ "now": [Function],
+ "onError": [Function],
+ "textComponent": Symbol(react.fragment),
+ "timeZone": null,
+ }
+ }
+ kibana={
+ Object {
+ "notifications": Object {
+ "toasts": Object {
+ "danger": [Function],
+ "show": [Function],
+ "success": [Function],
+ "warning": [Function],
+ },
+ },
+ "overlays": Object {
+ "openFlyout": [Function],
+ "openModal": [Function],
+ },
+ "services": Object {},
+ }
+ }
+/>
+`;
+
+exports[`AnnotationsTable Initialization with job config prop. 1`] = `
+
+`;
+
+exports[`AnnotationsTable Minimal initialization without props. 1`] = `
+
-
-`;
-
-exports[`AnnotationsTable Initialization with job config prop. 1`] = `
-
-
-
-
-
-`;
-
-exports[`AnnotationsTable Minimal initialization without props. 1`] = `
-
}
/>
`;
diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
index 9dabfce163db..d5025fd3c364 100644
--- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
+++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js
@@ -13,7 +13,6 @@
import uniq from 'lodash/uniq';
import PropTypes from 'prop-types';
-import rison from 'rison-node';
import React, { Component, Fragment } from 'react';
import memoizeOne from 'memoize-one';
import {
@@ -54,12 +53,15 @@ import {
ANNOTATION_EVENT_USER,
ANNOTATION_EVENT_DELAYED_DATA,
} from '../../../../../common/constants/annotations';
+import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public';
+import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { PLUGIN_ID } from '../../../../../common/constants/app';
const CURRENT_SERIES = 'current_series';
/**
* Table component for rendering the lists of annotations for an ML job.
*/
-export class AnnotationsTable extends Component {
+class AnnotationsTableUI extends Component {
static propTypes = {
annotations: PropTypes.array,
jobs: PropTypes.array,
@@ -199,7 +201,17 @@ export class AnnotationsTable extends Component {
}
}
- openSingleMetricView = (annotation = {}) => {
+ openSingleMetricView = async (annotation = {}) => {
+ const {
+ services: {
+ application: { navigateToApp },
+
+ share: {
+ urlGenerators: { getUrlGenerator },
+ },
+ },
+ } = this.props.kibana;
+
// Creates the link to the Single Metric Viewer.
// Set the total time range from the start to the end of the annotation.
const job = this.getJob(annotation.job_id);
@@ -210,30 +222,10 @@ export class AnnotationsTable extends Component {
);
const from = new Date(dataCounts.earliest_record_timestamp).toISOString();
const to = new Date(resultLatest).toISOString();
-
- const globalSettings = {
- ml: {
- jobIds: [job.job_id],
- },
- refreshInterval: {
- display: 'Off',
- pause: false,
- value: 0,
- },
- time: {
- from,
- to,
- mode: 'absolute',
- },
- };
-
- const appState = {
- query: {
- query_string: {
- analyze_wildcard: true,
- query: '*',
- },
- },
+ const timeRange = {
+ from,
+ to,
+ mode: 'absolute',
};
let mlTimeSeriesExplorer = {};
const entityCondition = {};
@@ -247,11 +239,11 @@ export class AnnotationsTable extends Component {
};
if (annotation.timestamp < dataCounts.earliest_record_timestamp) {
- globalSettings.time.from = new Date(annotation.timestamp).toISOString();
+ timeRange.from = new Date(annotation.timestamp).toISOString();
}
if (annotation.end_timestamp > dataCounts.latest_record_timestamp) {
- globalSettings.time.to = new Date(annotation.end_timestamp).toISOString();
+ timeRange.to = new Date(annotation.end_timestamp).toISOString();
}
}
@@ -274,14 +266,34 @@ export class AnnotationsTable extends Component {
entityCondition[annotation.by_field_name] = annotation.by_field_value;
}
mlTimeSeriesExplorer.entities = entityCondition;
- appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer;
+ // appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer;
- const _g = rison.encode(globalSettings);
- const _a = rison.encode(appState);
+ const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
+ const singleMetricViewerLink = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.SINGLE_METRIC_VIEWER,
+ pageState: {
+ timeRange,
+ refreshInterval: {
+ display: 'Off',
+ pause: false,
+ value: 0,
+ },
+ jobIds: [job.job_id],
+ query: {
+ query_string: {
+ analyze_wildcard: true,
+ query: '*',
+ },
+ },
+ ...mlTimeSeriesExplorer,
+ },
+ excludeBasePath: true,
+ });
- const url = `?_g=${_g}&_a=${_a}`;
- addItemToRecentlyAccessed('timeseriesexplorer', job.job_id, url);
- window.open(`#/timeseriesexplorer${url}`, '_self');
+ addItemToRecentlyAccessed('timeseriesexplorer', job.job_id, singleMetricViewerLink);
+ await navigateToApp(PLUGIN_ID, {
+ path: singleMetricViewerLink,
+ });
};
onMouseOverRow = (record) => {
@@ -686,3 +698,5 @@ export class AnnotationsTable extends Component {
);
}
}
+
+export const AnnotationsTable = withKibana(AnnotationsTableUI);
diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
index fdeab0c49e32..6025dd1c7433 100644
--- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
+++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
@@ -29,6 +29,8 @@ import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_util
import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils';
import { getIndexPatternIdFromName } from '../../util/index_utils';
import { replaceStringTokens } from '../../util/string_utils';
+import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../common/constants/ml_url_generator';
+import { PLUGIN_ID } from '../../../../common/constants/app';
/*
* Component for rendering the links menu inside a cell in the anomalies table.
*/
@@ -142,7 +144,18 @@ class LinksMenuUI extends Component {
}
};
- viewSeries = () => {
+ viewSeries = async () => {
+ const {
+ services: {
+ application: { navigateToApp },
+
+ share: {
+ urlGenerators: { getUrlGenerator },
+ },
+ },
+ } = this.props.kibana;
+ const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
+
const record = this.props.anomaly.source;
const bounds = this.props.bounds;
const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z
@@ -171,44 +184,36 @@ class LinksMenuUI extends Component {
entityCondition[record.by_field_name] = record.by_field_value;
}
- // Use rison to build the URL .
- const _g = rison.encode({
- ml: {
+ const singleMetricViewerLink = await mlUrlGenerator.createUrl({
+ excludeBasePath: true,
+ page: ML_PAGES.SINGLE_METRIC_VIEWER,
+ pageState: {
jobIds: [record.job_id],
- },
- refreshInterval: {
- display: 'Off',
- pause: false,
- value: 0,
- },
- time: {
- from: from,
- to: to,
- mode: 'absolute',
- },
- });
-
- const _a = rison.encode({
- mlTimeSeriesExplorer: {
+ refreshInterval: {
+ display: 'Off',
+ pause: false,
+ value: 0,
+ },
+ timeRange: {
+ from: from,
+ to: to,
+ mode: 'absolute',
+ },
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: record.detector_index,
entities: entityCondition,
- },
- query: {
query_string: {
analyze_wildcard: true,
query: '*',
},
},
});
-
- // Need to encode the _a parameter in case any entities contain unsafe characters such as '+'.
- let path = '#/timeseriesexplorer';
- path += `?_g=${_g}&_a=${encodeURIComponent(_a)}`;
- window.open(path, '_blank');
+ await navigateToApp(PLUGIN_ID, {
+ path: singleMetricViewerLink,
+ });
};
viewExamples = () => {
diff --git a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx
index 4a63a8cd7e71..d54a7fe81e85 100644
--- a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx
+++ b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx
@@ -6,13 +6,22 @@
import React from 'react';
import { Router } from 'react-router-dom';
-import { render, fireEvent } from '@testing-library/react';
+import { render } from '@testing-library/react';
import { createBrowserHistory } from 'history';
import { I18nProvider } from '@kbn/i18n/react';
import { AnomalyResultsViewSelector } from './index';
+jest.mock('../../contexts/kibana', () => {
+ return {
+ useMlUrlGenerator: () => ({
+ createUrl: jest.fn(),
+ }),
+ useNavigateToPath: () => jest.fn(),
+ };
+});
+
describe('AnomalyResultsViewSelector', () => {
test('should create selector with correctly selected value', () => {
const history = createBrowserHistory();
@@ -31,27 +40,4 @@ describe('AnomalyResultsViewSelector', () => {
getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer').hasAttribute('checked')
).toBe(true);
});
-
- test('should open window to other results view when clicking on non-checked input', () => {
- // Create mock for window.open
- const mockedOpen = jest.fn();
- const originalOpen = window.open;
- window.open = mockedOpen;
-
- const history = createBrowserHistory();
-
- const { getByTestId } = render(
-
-
-
-
-
- );
-
- fireEvent.click(getByTestId('mlAnomalyResultsViewSelectorExplorer'));
- expect(mockedOpen).toHaveBeenCalledWith('#/explorer', '_self');
-
- // Clean-up window.open.
- window.open = originalOpen;
- });
});
diff --git a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
index 78acb422851e..c4c8f06bbbc3 100644
--- a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
+++ b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx
@@ -5,21 +5,25 @@
*/
import React, { FC, useMemo } from 'react';
-import { encode } from 'rison-node';
import { EuiButtonGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '../../util/url_state';
+import { useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
+import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
interface Props {
- viewId: 'timeseriesexplorer' | 'explorer';
+ viewId: typeof ML_PAGES.SINGLE_METRIC_VIEWER | typeof ML_PAGES.ANOMALY_EXPLORER;
}
// Component for rendering a set of buttons for switching between the Anomaly Detection results views.
export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
+ const urlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
const toggleButtonsIcons = useMemo(
() => [
{
@@ -28,7 +32,7 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
defaultMessage: 'View results in the Single Metric Viewer',
}),
iconType: 'visLine',
- value: 'timeseriesexplorer',
+ value: ML_PAGES.SINGLE_METRIC_VIEWER,
'data-test-subj': 'mlAnomalyResultsViewSelectorSingleMetricViewer',
},
{
@@ -37,7 +41,7 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
defaultMessage: 'View results in the Anomaly Explorer',
}),
iconType: 'visTable',
- value: 'explorer',
+ value: ML_PAGES.ANOMALY_EXPLORER,
'data-test-subj': 'mlAnomalyResultsViewSelectorExplorer',
},
],
@@ -46,9 +50,14 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
const [globalState] = useUrlState('_g');
- const onChangeView = (newViewId: string) => {
- const fullGlobalStateString = globalState !== undefined ? `?_g=${encode(globalState)}` : '';
- window.open(`#/${newViewId}${fullGlobalStateString}`, '_self');
+ const onChangeView = async (newViewId: Props['viewId']) => {
+ const url = await urlGenerator.createUrl({
+ page: newViewId,
+ pageState: {
+ globalState,
+ },
+ });
+ await navigateToPath(url);
};
return (
@@ -60,7 +69,7 @@ export const AnomalyResultsViewSelector: FC = ({ viewId }) => {
data-test-subj="mlAnomalyResultsViewSelector"
options={toggleButtonsIcons}
idSelected={viewId}
- onChange={onChangeView}
+ onChange={(newViewId: string) => onChangeView(newViewId as Props['viewId'])}
isIconOnly
/>
);
diff --git a/x-pack/plugins/ml/public/application/components/custom_hooks/use_create_ad_links.ts b/x-pack/plugins/ml/public/application/components/custom_hooks/use_create_ad_links.ts
index 368e758a027c..b4668810b942 100644
--- a/x-pack/plugins/ml/public/application/components/custom_hooks/use_create_ad_links.ts
+++ b/x-pack/plugins/ml/public/application/components/custom_hooks/use_create_ad_links.ts
@@ -22,16 +22,19 @@ export const useCreateADLinks = () => {
const userTimeSettings = useUiSettings().get(ANOMALY_DETECTION_DEFAULT_TIME_RANGE);
const createLinkWithUserDefaults = useCallback(
(location, jobList) => {
- const resultsPageUrl = mlJobService.createResultsUrlForJobs(
+ return mlJobService.createResultsUrlForJobs(
jobList,
location,
useUserTimeSettings === true && userTimeSettings !== undefined
? userTimeSettings
: undefined
);
- return `${basePath.get()}/app/ml${resultsPageUrl}`;
},
[basePath]
);
return { createLinkWithUserDefaults };
};
+
+export type CreateLinkWithUserDefaults = ReturnType<
+ typeof useCreateADLinks
+>['createLinkWithUserDefaults'];
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
index 22815fe593d5..6aad5d53c3a3 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
+++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
@@ -32,6 +32,7 @@ import { UseIndexDataReturnType } from './types';
import { DecisionPathPopover } from './feature_importance/decision_path_popover';
import { TopClasses } from '../../../../common/types/feature_importance';
import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics';
+import { DataFrameAnalysisConfigType } from '../../../../common/types/data_frame_analytics';
// TODO Fix row hovering + bar highlighting
// import { hoveredRow$ } from './column_chart';
@@ -44,7 +45,7 @@ export const DataGridTitle: FC<{ title: string }> = ({ title }) => (
interface PropsWithoutHeader extends UseIndexDataReturnType {
baseline?: number;
- analysisType?: ANALYSIS_CONFIG_TYPE;
+ analysisType?: DataFrameAnalysisConfigType;
resultsField?: string;
dataTestSubj: string;
toastNotifications: CoreSetup['notifications']['toasts'];
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx b/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx
index 263337f93e9a..7c4428db71b3 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx
+++ b/x-pack/plugins/ml/public/application/components/data_grid/feature_importance/decision_path_popover.tsx
@@ -13,10 +13,11 @@ import { FeatureImportance, TopClasses } from '../../../../../common/types/featu
import { ANALYSIS_CONFIG_TYPE } from '../../../data_frame_analytics/common';
import { ClassificationDecisionPath } from './decision_path_classification';
import { useMlKibana } from '../../../contexts/kibana';
+import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
interface DecisionPathPopoverProps {
featureImportance: FeatureImportance[];
- analysisType: ANALYSIS_CONFIG_TYPE;
+ analysisType: DataFrameAnalysisConfigType;
predictionFieldName?: string;
baseline?: number;
predictedValue?: number | string | undefined;
diff --git a/x-pack/plugins/ml/public/application/components/data_recognizer/recognized_result.js b/x-pack/plugins/ml/public/application/components/data_recognizer/recognized_result.js
index 1f03dbe13475..279afc8c5033 100644
--- a/x-pack/plugins/ml/public/application/components/data_recognizer/recognized_result.js
+++ b/x-pack/plugins/ml/public/application/components/data_recognizer/recognized_result.js
@@ -9,11 +9,16 @@ import PropTypes from 'prop-types';
import { EuiIcon, EuiFlexItem } from '@elastic/eui';
import { CreateJobLinkCard } from '../create_job_link_card';
+import { useMlKibana } from '../../contexts/kibana';
export const RecognizedResult = ({ config, indexPattern, savedSearch }) => {
+ const {
+ services: {
+ http: { basePath },
+ },
+ } = useMlKibana();
const id = savedSearch === null ? `index=${indexPattern.id}` : `savedSearchId=${savedSearch.id}`;
-
- const href = `#/jobs/new_job/recognize?id=${config.id}&${id}`;
+ const href = `${basePath.get()}/app/ml/jobs/new_job/recognize?id=${config.id}&${id}`;
let logo = null;
// if a logo is available, use that, otherwise display the id
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
index 3a4875fa243f..671f0b196ce3 100644
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
+++ b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
@@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, useState } from 'react';
-import { encode } from 'rison-node';
+import React, { FC, useState, useEffect } from 'react';
-import { EuiTabs, EuiTab, EuiLink } from '@elastic/eui';
+import { EuiTabs, EuiTab } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-
-import { useUrlState } from '../../util/url_state';
-
import { TabId } from './navigation_menu';
+import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
+import { MlUrlGeneratorState } from '../../../../common/types/ml_url_generator';
+import { useUrlState } from '../../util/url_state';
+import { ML_APP_NAME } from '../../../../common/constants/app';
export interface Tab {
id: TabId;
@@ -66,20 +66,57 @@ function getTabs(disableLinks: boolean): Tab[] {
}
interface TabData {
testSubject: string;
- pathId?: string;
+ pathId?: MlUrlGeneratorState['page'];
+ name: string;
}
const TAB_DATA: Record = {
- overview: { testSubject: 'mlMainTab overview' },
+ overview: {
+ testSubject: 'mlMainTab overview',
+ name: i18n.translate('xpack.ml.overviewTabLabel', {
+ defaultMessage: 'Overview',
+ }),
+ },
// Note that anomaly detection jobs list is mapped to ml#/jobs.
- anomaly_detection: { testSubject: 'mlMainTab anomalyDetection', pathId: 'jobs' },
- data_frame_analytics: { testSubject: 'mlMainTab dataFrameAnalytics' },
- datavisualizer: { testSubject: 'mlMainTab dataVisualizer' },
- settings: { testSubject: 'mlMainTab settings' },
- 'access-denied': { testSubject: 'mlMainTab overview' },
+ anomaly_detection: {
+ testSubject: 'mlMainTab anomalyDetection',
+ name: i18n.translate('xpack.ml.anomalyDetectionTabLabel', {
+ defaultMessage: 'Anomaly Detection',
+ }),
+ pathId: 'jobs',
+ },
+ data_frame_analytics: {
+ testSubject: 'mlMainTab dataFrameAnalytics',
+ name: i18n.translate('xpack.ml.dataFrameAnalyticsTabLabel', {
+ defaultMessage: 'Data Frame Analytics',
+ }),
+ },
+ datavisualizer: {
+ testSubject: 'mlMainTab dataVisualizer',
+ name: i18n.translate('xpack.ml.dataVisualizerTabLabel', {
+ defaultMessage: 'Data Visualizer',
+ }),
+ },
+ settings: {
+ testSubject: 'mlMainTab settings',
+ name: i18n.translate('xpack.ml.settingsTabLabel', {
+ defaultMessage: 'Settings',
+ }),
+ },
+ 'access-denied': {
+ testSubject: 'mlMainTab overview',
+ name: i18n.translate('xpack.ml.accessDeniedTabLabel', {
+ defaultMessage: 'Access Denied',
+ }),
+ },
};
export const MainTabs: FC = ({ tabId, disableLinks }) => {
+ const {
+ services: {
+ chrome: { docTitle },
+ },
+ } = useMlKibana();
const [globalState] = useUrlState('_g');
const [selectedTabId, setSelectedTabId] = useState(tabId);
function onSelectedTabChanged(id: TabId) {
@@ -87,16 +124,40 @@ export const MainTabs: FC = ({ tabId, disableLinks }) => {
}
const tabs = getTabs(disableLinks);
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
+ const redirectToTab = async (defaultPathId: MlUrlGeneratorState['page']) => {
+ const pageState =
+ globalState?.refreshInterval !== undefined
+ ? {
+ globalState: {
+ refreshInterval: globalState.refreshInterval,
+ },
+ }
+ : undefined;
+ // TODO - Fix ts so passing pageState won't default to MlGenericUrlState when pageState is passed in
+ // @ts-ignore
+ const path = await mlUrlGenerator.createUrl({
+ page: defaultPathId,
+ // only retain the refreshInterval part of globalState
+ // appState will not be considered.
+ pageState,
+ });
+
+ await navigateToPath(path, false);
+ };
+
+ useEffect(() => {
+ docTitle.change([TAB_DATA[selectedTabId].name, ML_APP_NAME]);
+ }, [selectedTabId]);
return (
{tabs.map((tab: Tab) => {
const { id, disabled } = tab;
const testSubject = TAB_DATA[id].testSubject;
- const defaultPathId = TAB_DATA[id].pathId || id;
- // globalState (e.g. selected jobs and time range) should be retained when changing pages.
- // appState will not be considered.
- const fullGlobalStateString = globalState !== undefined ? `?_g=${encode(globalState)}` : '';
+ const defaultPathId = (TAB_DATA[id].pathId || id) as MlUrlGeneratorState['page'];
return disabled ? (
@@ -104,21 +165,18 @@ export const MainTabs: FC = ({ tabId, disableLinks }) => {
) : (
- {
+ onSelectedTabChanged(id);
+ redirectToTab(defaultPathId);
+ }}
+ isSelected={id === selectedTabId}
+ key={`tab-${id}-key`}
>
- onSelectedTabChanged(id)}
- isSelected={id === selectedTabId}
- key={`tab-${id}-key`}
- >
- {tab.name}
-
-
+ {tab.name}
+
);
})}
diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.js
index 48e0da72f067..eb12cb767967 100644
--- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.js
+++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.js
@@ -17,8 +17,19 @@ import { ScopeExpression } from './scope_expression';
import { checkPermission } from '../../capabilities/check_capabilities';
import { getScopeFieldDefaults } from './utils';
import { FormattedMessage } from '@kbn/i18n/react';
+import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
+import { useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
function NoFilterListsCallOut() {
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+ const redirectToFilterManagementPage = async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.FILTER_LISTS_MANAGE,
+ });
+ await navigateToPath(path, true);
+ };
+
return (
+
useKibana();
export type MlKibanaReactContextValue = KibanaReactContextValue;
diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts
index 48385ad3ae6a..d448185c914b 100644
--- a/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts
+++ b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts
@@ -4,8 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { useCallback, useEffect, useState } from 'react';
import { useMlKibana } from './kibana_context';
import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator';
+import { MlUrlGeneratorState } from '../../../../common/types/ml_url_generator';
+import { useUrlState } from '../../util/url_state';
export const useMlUrlGenerator = () => {
const {
@@ -18,3 +21,59 @@ export const useMlUrlGenerator = () => {
return getUrlGenerator(ML_APP_URL_GENERATOR);
};
+
+export const useMlLink = (params: MlUrlGeneratorState): string => {
+ const [href, setHref] = useState(params.page);
+ const mlUrlGenerator = useMlUrlGenerator();
+
+ useEffect(() => {
+ let isCancelled = false;
+ const generateUrl = async (_params: MlUrlGeneratorState) => {
+ const url = await mlUrlGenerator.createUrl(_params);
+ if (!isCancelled) {
+ setHref(url);
+ }
+ };
+ generateUrl(params);
+ return () => {
+ isCancelled = true;
+ };
+ }, [params]);
+
+ return href;
+};
+
+export const useCreateAndNavigateToMlLink = (
+ page: MlUrlGeneratorState['page']
+): (() => Promise) => {
+ const mlUrlGenerator = useMlUrlGenerator();
+ const [globalState] = useUrlState('_g');
+
+ const {
+ services: {
+ application: { navigateToUrl },
+ },
+ } = useMlKibana();
+
+ const redirectToMlPage = useCallback(
+ async (_page: MlUrlGeneratorState['page']) => {
+ const pageState =
+ globalState?.refreshInterval !== undefined
+ ? {
+ globalState: {
+ refreshInterval: globalState.refreshInterval,
+ },
+ }
+ : undefined;
+
+ // TODO: fix ts only interpreting it as MlUrlGenericState if pageState is passed
+ // @ts-ignore
+ const url = await mlUrlGenerator.createUrl({ page: _page, pageState });
+ await navigateToUrl(url);
+ },
+ [mlUrlGenerator, navigateToUrl]
+ );
+
+ // returns the onClick callback
+ return useCallback(() => redirectToMlPage(page), [redirectToMlPage, page]);
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
index 60681fb6e7bb..d22bba7738db 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
@@ -15,8 +15,8 @@ import { SavedSearchQuery } from '../../contexts/ml';
import {
AnalysisConfig,
ClassificationAnalysis,
+ DataFrameAnalysisConfigType,
RegressionAnalysis,
- ANALYSIS_CONFIG_TYPE,
} from '../../../../common/types/data_frame_analytics';
import {
isOutlierAnalysis,
@@ -26,6 +26,7 @@ import {
getDependentVar,
getPredictedFieldName,
} from '../../../../common/util/analytics_utils';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../common/constants/data_frame_analytics';
export type IndexPattern = string;
export enum ANALYSIS_ADVANCED_FIELDS {
@@ -429,7 +430,7 @@ interface LoadEvalDataConfig {
predictionFieldName?: string;
searchQuery?: ResultsSearchQuery;
ignoreDefaultQuery?: boolean;
- jobType: ANALYSIS_CONFIG_TYPE;
+ jobType: DataFrameAnalysisConfigType;
requiresKeyword?: boolean;
}
@@ -550,7 +551,7 @@ export {
isRegressionAnalysis,
isClassificationAnalysis,
getPredictionFieldName,
- ANALYSIS_CONFIG_TYPE,
getDependentVar,
getPredictedFieldName,
+ ANALYSIS_CONFIG_TYPE,
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
index 00d735d9a866..83eebccd310e 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts
@@ -14,7 +14,6 @@ export {
UpdateDataFrameAnalyticsConfig,
IndexPattern,
REFRESH_ANALYTICS_LIST_STATE,
- ANALYSIS_CONFIG_TYPE,
OUTLIER_ANALYSIS_METHOD,
RegressionEvaluateResponse,
getValuesFromResponse,
@@ -26,6 +25,7 @@ export {
SEARCH_SIZE,
defaultSearchQuery,
SearchQuery,
+ ANALYSIS_CONFIG_TYPE,
} from './analytics';
export {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx
index 1e5dbee3499b..1e6a616fedd6 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/job_type.tsx
@@ -8,7 +8,7 @@ import React, { Fragment, FC } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiSelect } from '@elastic/eui';
-import { ANALYSIS_CONFIG_TYPE } from '../../../../common';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics';
import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx
index 88c89df86b29..310cd4e3b3a7 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx
@@ -16,6 +16,7 @@ import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../com
import { CATEGORICAL_TYPES } from './form_options_validation';
import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public';
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
const containsClassificationFieldsCb = ({ name, type }: Field) =>
!OMIT_FIELDS.includes(name) &&
@@ -32,13 +33,13 @@ const containsRegressionFieldsCb = ({ name, type }: Field) =>
const containsOutlierFieldsCb = ({ name, type }: Field) =>
!OMIT_FIELDS.includes(name) && name !== EVENT_RATE_FIELD_ID && BASIC_NUMERICAL_TYPES.has(type);
-const callbacks: Record boolean> = {
+const callbacks: Record boolean> = {
[ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: containsClassificationFieldsCb,
[ANALYSIS_CONFIG_TYPE.REGRESSION]: containsRegressionFieldsCb,
[ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: containsOutlierFieldsCb,
};
-const messages: Record = {
+const messages: Record = {
[ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: (
= ({ jobId, analysisType }) => {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
index eea579ef1d06..84b1c4241aaf 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
@@ -29,7 +29,6 @@ import {
SEARCH_SIZE,
defaultSearchQuery,
getAnalysisType,
- ANALYSIS_CONFIG_TYPE,
} from '../../../../common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
@@ -39,6 +38,7 @@ import { IndexPatternPrompt } from '../index_pattern_prompt';
import { useExplorationResults } from './use_exploration_results';
import { useMlKibana } from '../../../../../contexts/kibana';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
const showingDocs = i18n.translate(
'xpack.ml.dataframe.analytics.explorationResults.documentsShownHelpText',
@@ -195,7 +195,7 @@ export const ExplorationResultsTable: FC = React.memo(
{...classificationData}
dataTestSubj="mlExplorationDataGrid"
toastNotifications={getToastNotifications()}
- analysisType={(analysisType as unknown) as ANALYSIS_CONFIG_TYPE}
+ analysisType={(analysisType as unknown) as DataFrameAnalysisConfigType}
/>
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
index c8349084dbda..f4f01330271f 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx
@@ -26,11 +26,12 @@ import { OutlierExploration } from './components/outlier_exploration';
import { RegressionExploration } from './components/regression_exploration';
import { ClassificationExploration } from './components/classification_exploration';
-import { ANALYSIS_CONFIG_TYPE } from '../../common/analytics';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../../common/constants/data_frame_analytics';
+import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
export const Page: FC<{
jobId: string;
- analysisType: ANALYSIS_CONFIG_TYPE;
+ analysisType: DataFrameAnalysisConfigType;
}> = ({ jobId, analysisType }) => (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
index a3595b51d0a5..2363e6fbecc9 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/use_view_action.tsx
@@ -7,24 +7,32 @@
import React, { useCallback, useMemo } from 'react';
import { getAnalysisType } from '../../../../common/analytics';
-import { useNavigateToPath } from '../../../../../contexts/kibana';
+import { useMlUrlGenerator, useNavigateToPath } from '../../../../../contexts/kibana';
-import {
- getResultsUrl,
- DataFrameAnalyticsListAction,
- DataFrameAnalyticsListRow,
-} from '../analytics_list/common';
+import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
import { getViewLinkStatus } from './get_view_link_status';
import { viewActionButtonText, ViewButton } from './view_button';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
+import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
export type ViewAction = ReturnType;
export const useViewAction = () => {
+ const mlUrlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();
+ const redirectToTab = async (jobId: string, analysisType: DataFrameAnalysisConfigType) => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
+ pageState: { jobId, analysisType },
+ });
+
+ await navigateToPath(path, false);
+ };
+
const clickHandler = useCallback((item: DataFrameAnalyticsListRow) => {
- const analysisType = getAnalysisType(item.config.analysis);
- navigateToPath(getResultsUrl(item.id, analysisType));
+ const analysisType = getAnalysisType(item.config.analysis) as DataFrameAnalysisConfigType;
+ redirectToTab(item.id, analysisType);
}, []);
const action: DataFrameAnalyticsListAction = useMemo(
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
index 0c3bff58c25c..2f8e087a6a3f 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
@@ -15,12 +15,8 @@ import {
EuiSearchBarProps,
EuiSpacer,
} from '@elastic/eui';
-
-import {
- DataFrameAnalyticsId,
- useRefreshAnalyticsList,
- ANALYSIS_CONFIG_TYPE,
-} from '../../../../common';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics';
+import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
index 994357412510..37076d400f02 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts
@@ -9,11 +9,8 @@ import { EuiTableActionsColumnType, Query, Ast } from '@elastic/eui';
import { DATA_FRAME_TASK_STATE } from './data_frame_task_state';
export { DATA_FRAME_TASK_STATE };
-import {
- DataFrameAnalyticsId,
- DataFrameAnalyticsConfig,
- ANALYSIS_CONFIG_TYPE,
-} from '../../../../common';
+import { DataFrameAnalyticsId, DataFrameAnalyticsConfig } from '../../../../common';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
export enum DATA_FRAME_MODE {
BATCH = 'batch',
@@ -111,10 +108,7 @@ export interface DataFrameAnalyticsListRow {
checkpointing: object;
config: DataFrameAnalyticsConfig;
id: DataFrameAnalyticsId;
- job_type:
- | ANALYSIS_CONFIG_TYPE.CLASSIFICATION
- | ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION
- | ANALYSIS_CONFIG_TYPE.REGRESSION;
+ job_type: DataFrameAnalysisConfigType;
mode: string;
state: DataFrameAnalyticsStats['state'];
stats: DataFrameAnalyticsStats;
@@ -137,10 +131,6 @@ export function isCompletedAnalyticsJob(stats: DataFrameAnalyticsStats) {
return stats.state === DATA_FRAME_TASK_STATE.STOPPED && progress === 100;
}
-export function getResultsUrl(jobId: string, analysisType: ANALYSIS_CONFIG_TYPE | string) {
- return `#/data_frame_analytics/exploration?_g=(ml:(jobId:${jobId},analysisType:${analysisType}))`;
-}
-
// The single Action type is not exported as is
// from EUI so we use that code to get the single
// Action type from the array of actions.
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx
index ef1d373a55a1..1af99d2a1ed0 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx
@@ -19,8 +19,6 @@ import {
EuiLink,
RIGHT_ALIGNMENT,
} from '@elastic/eui';
-import { getJobIdUrl, TAB_IDS } from '../../../../../util/get_selected_ids_url';
-
import { getAnalysisType, DataFrameAnalyticsId } from '../../../../common';
import {
getDataFrameAnalyticsProgressPhase,
@@ -32,6 +30,8 @@ import {
DataFrameAnalyticsStats,
} from './common';
import { useActions } from './use_actions';
+import { useMlLink } from '../../../../../contexts/kibana';
+import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
enum TASK_STATE_COLOR {
analyzing = 'primary',
@@ -134,9 +134,14 @@ export const progressColumn = {
'data-test-subj': 'mlAnalyticsTableColumnProgress',
};
-export const getDFAnalyticsJobIdLink = (item: DataFrameAnalyticsListRow) => (
- {item.id}
-);
+export const DFAnalyticsJobIdLink = ({ item }: { item: DataFrameAnalyticsListRow }) => {
+ const href = useMlLink({
+ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
+ pageState: { jobId: item.id },
+ });
+
+ return {item.id};
+};
export const useColumns = (
expandedRowItemIds: DataFrameAnalyticsId[],
@@ -145,7 +150,6 @@ export const useColumns = (
isMlEnabledInSpace: boolean = true
) => {
const { actions, modals } = useActions(isManagementTable);
-
function toggleDetails(item: DataFrameAnalyticsListRow) {
const index = expandedRowItemIds.indexOf(item.config.id);
if (index !== -1) {
@@ -200,7 +204,7 @@ export const useColumns = (
'data-test-subj': 'mlAnalyticsTableColumnId',
scope: 'row',
render: (item: DataFrameAnalyticsListRow) =>
- isManagementTable ? getDFAnalyticsJobIdLink(item) : item.id,
+ isManagementTable ? : item.id,
},
{
field: DataFrameAnalyticsListColumn.description,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
index 338b6444671a..dbc7a23f2258 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx
@@ -29,21 +29,23 @@ import { useInferenceApiService } from '../../../../../services/ml_api_service/i
import { ModelsTableToConfigMapping } from './index';
import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
import { DeleteModelsModal } from './delete_models_modal';
-import { useMlKibana, useNotifications } from '../../../../../contexts/kibana';
+import { useMlKibana, useMlUrlGenerator, useNotifications } from '../../../../../contexts/kibana';
import { ExpandedRow } from './expanded_row';
-import { getResultsUrl } from '../analytics_list/common';
import {
ModelConfigResponse,
ModelPipelines,
TrainedModelStat,
} from '../../../../../../../common/types/inference';
import {
+ getAnalysisType,
REFRESH_ANALYTICS_LIST_STATE,
refreshAnalyticsList$,
useRefreshAnalyticsList,
} from '../../../../common';
import { useTableSettings } from '../analytics_list/use_table_settings';
import { filterAnalyticsModels, AnalyticsSearchBar } from '../analytics_search_bar';
+import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
type Stats = Omit;
@@ -61,6 +63,7 @@ export const ModelsList: FC = () => {
application: { navigateToUrl, capabilities },
},
} = useMlKibana();
+ const urlGenerator = useMlUrlGenerator();
const canDeleteDataFrameAnalytics = capabilities.ml.canDeleteDataFrameAnalytics as boolean;
@@ -278,12 +281,19 @@ export const ModelsList: FC = () => {
type: 'icon',
available: (item) => item.metadata?.analytics_config?.id,
onClick: async (item) => {
- await navigateToUrl(
- getResultsUrl(
- item.metadata?.analytics_config.id,
- Object.keys(item.metadata?.analytics_config.analysis)[0]
- )
- );
+ if (item.metadata?.analytics_config === undefined) return;
+
+ const url = await urlGenerator.createUrl({
+ page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
+ pageState: {
+ jobId: item.metadata?.analytics_config.id as string,
+ analysisType: getAnalysisType(
+ item.metadata?.analytics_config.analysis
+ ) as DataFrameAnalysisConfigType,
+ },
+ });
+
+ await navigateToUrl(url);
},
isPrimary: true,
},
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
index 7cd9fcc052f1..178638322bac 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
@@ -33,13 +33,13 @@ import {
JOB_ID_MAX_LENGTH,
ALLOWED_DATA_UNITS,
} from '../../../../../../../common/constants/validation';
+import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics';
import {
getDependentVar,
getNumTopFeatureImportanceValues,
getTrainingPercent,
isRegressionAnalysis,
isClassificationAnalysis,
- ANALYSIS_CONFIG_TYPE,
NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN,
TRAINING_PERCENT_MIN,
TRAINING_PERCENT_MAX,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index 4926decaa7f9..2a89c5a5fd68 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -8,13 +8,14 @@ import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/com
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../../ml_nodes_check';
-import { ANALYSIS_CONFIG_TYPE, defaultSearchQuery } from '../../../../common/analytics';
+import { defaultSearchQuery, getAnalysisType } from '../../../../common/analytics';
import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone';
import {
DataFrameAnalyticsConfig,
DataFrameAnalyticsId,
+ DataFrameAnalysisConfigType,
} from '../../../../../../../common/types/data_frame_analytics';
-
+import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics';
export enum DEFAULT_MODEL_MEMORY_LIMIT {
regression = '100mb',
outlier_detection = '50mb',
@@ -28,7 +29,7 @@ export const UNSET_CONFIG_ITEM = '--';
export type EsIndexName = string;
export type DependentVariable = string;
export type IndexPatternTitle = string;
-export type AnalyticsJobType = ANALYSIS_CONFIG_TYPE | undefined;
+export type AnalyticsJobType = DataFrameAnalysisConfigType | undefined;
type IndexPatternId = string;
export type SourceIndexMap = Record<
IndexPatternTitle,
@@ -290,7 +291,7 @@ export function getFormStateFromJobConfig(
analyticsJobConfig: Readonly,
isClone: boolean = true
): Partial {
- const jobType = Object.keys(analyticsJobConfig.analysis)[0] as ANALYSIS_CONFIG_TYPE;
+ const jobType = getAnalysisType(analyticsJobConfig.analysis) as DataFrameAnalysisConfigType;
const resultState: Partial = {
jobType,
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
index 41f3bab8113f..14427dd5c6ef 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
@@ -11,7 +11,7 @@ import {
GetDataFrameAnalyticsStatsResponseOk,
} from '../../../../../services/ml_api_service/data_frame_analytics';
import {
- ANALYSIS_CONFIG_TYPE,
+ getAnalysisType,
REFRESH_ANALYTICS_LIST_STATE,
refreshAnalyticsList$,
} from '../../../../common';
@@ -25,6 +25,7 @@ import {
isDataFrameAnalyticsStopped,
} from '../../components/analytics_list/common';
import { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
+import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
export const isGetDataFrameAnalyticsStatsResponseOk = (
arg: any
@@ -143,7 +144,7 @@ export const getAnalyticsFactory = (
checkpointing: {},
config,
id: config.id,
- job_type: Object.keys(config.analysis)[0] as ANALYSIS_CONFIG_TYPE,
+ job_type: getAnalysisType(config.analysis) as DataFrameAnalysisConfigType,
mode: DATA_FRAME_MODE.BATCH,
state: stats.state,
stats,
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx
index 769b83c03110..7c30dc0cac69 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx
+++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx
@@ -52,7 +52,10 @@ function startTrialDescription() {
export const DatavisualizerSelector: FC = () => {
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const {
- services: { licenseManagement },
+ services: {
+ licenseManagement,
+ http: { basePath },
+ },
} = useMlKibana();
const navigateToPath = useNavigateToPath();
@@ -183,7 +186,10 @@ export const DatavisualizerSelector: FC = () => {
}
description={startTrialDescription()}
footer={
-
+
= ({
to: 'now',
});
const [showCreateJobLink, setShowCreateJobLink] = useState(false);
- const [globalStateString, setGlobalStateString] = useState('');
+ const [globalState, setGlobalState] = useState();
+
+ const [discoverLink, setDiscoverLink] = useState('');
const {
services: {
http: { basePath },
},
} = useMlKibana();
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
+ const {
+ services: {
+ share: {
+ urlGenerators: { getUrlGenerator },
+ },
+ },
+ } = useMlKibana();
+
+ useEffect(() => {
+ let unmounted = false;
+
+ const getDiscoverUrl = async (): Promise => {
+ const state: DiscoverUrlGeneratorState = {
+ indexPatternId,
+ };
+
+ if (globalState?.time) {
+ state.timeRange = globalState.time;
+ }
+ if (!unmounted) {
+ const discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR);
+ const discoverUrl = await discoverUrlGenerator.createUrl(state);
+ setDiscoverLink(discoverUrl);
+ }
+ };
+ getDiscoverUrl();
+
+ return () => {
+ unmounted = true;
+ };
+ }, [indexPatternId, getUrlGenerator]);
+
+ const openInDataVisualizer = useCallback(async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
+ pageState: {
+ index: indexPatternId,
+ globalState,
+ },
+ });
+ await navigateToPath(path);
+ }, [indexPatternId, globalState]);
+
+ const redirectToADCreateJobsSelectTypePage = useCallback(async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
+ pageState: {
+ index: indexPatternId,
+ globalState,
+ },
+ });
+ await navigateToPath(path);
+ }, [indexPatternId, globalState]);
useEffect(() => {
setShowCreateJobLink(checkPermission('canCreateJob') && mlNodesAvailable());
@@ -49,11 +113,13 @@ export const ResultsLinks: FC = ({
}, []);
useEffect(() => {
- const _g =
- timeFieldName !== undefined
- ? `&_g=(time:(from:'${duration.from}',mode:quick,to:'${duration.to}'))`
- : '';
- setGlobalStateString(_g);
+ const _globalState: MlCommonGlobalState = {
+ time: {
+ from: duration.from,
+ to: duration.to,
+ },
+ };
+ setGlobalState(_globalState);
}, [duration]);
async function updateTimeValues(recheck = true) {
@@ -89,7 +155,7 @@ export const ResultsLinks: FC = ({
/>
}
description=""
- href={`${basePath.get()}/app/discover#/?&_a=(index:'${indexPatternId}')${globalStateString}`}
+ href={discoverLink}
/>
)}
@@ -108,7 +174,7 @@ export const ResultsLinks: FC = ({
/>
}
description=""
- href={`#/jobs/new_job/step/job_type?index=${indexPatternId}${globalStateString}`}
+ onClick={redirectToADCreateJobsSelectTypePage}
/>
)}
@@ -124,7 +190,7 @@ export const ResultsLinks: FC = ({
/>
}
description=""
- href={`#/jobs/new_job/datavisualizer?index=${indexPatternId}${globalStateString}`}
+ onClick={openInDataVisualizer}
/>
)}
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
index 1f2c97b128e3..ab738ca0f154 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
+++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
@@ -9,11 +9,11 @@ import React, { FC, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui';
-
+import { Link } from 'react-router-dom';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
import { CreateJobLinkCard } from '../../../../components/create_job_link_card';
import { DataRecognizer } from '../../../../components/data_recognizer';
-import { getBasePath } from '../../../../util/dependency_cache';
+import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
interface Props {
indexPattern: IndexPattern;
@@ -21,7 +21,6 @@ interface Props {
export const ActionsPanel: FC = ({ indexPattern }) => {
const [recognizerResultsCount, setRecognizerResultsCount] = useState(0);
- const basePath = getBasePath();
const recognizerResults = {
count: 0,
@@ -29,12 +28,7 @@ export const ActionsPanel: FC = ({ indexPattern }) => {
setRecognizerResultsCount(recognizerResults.count);
},
};
-
- function openAdvancedJobWizard() {
- // TODO - pass the search string to the advanced job page as well as the index pattern
- // (add in with new advanced job wizard?)
- window.open(`${basePath.get()}/app/ml/jobs/new_job/advanced?index=${indexPattern.id}`, '_self');
- }
+ const createJobLink = `/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB}/advanced?index=${indexPattern.id}`;
// Note we use display:none for the DataRecognizer section as it needs to be
// passed the recognizerResults object, and then run the recognizer check which
@@ -78,19 +72,19 @@ export const ActionsPanel: FC = ({ indexPattern }) => {
-
+
+
+
);
};
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/__snapshots__/explorer_no_jobs_found.test.js.snap b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/__snapshots__/explorer_no_jobs_found.test.js.snap
index c6503a639997..826f7b707cfd 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/__snapshots__/explorer_no_jobs_found.test.js.snap
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/__snapshots__/explorer_no_jobs_found.test.js.snap
@@ -3,17 +3,20 @@
exports[`ExplorerNoInfluencersFound snapshot 1`] = `
-
-
+
+
+
+
}
data-test-subj="mlNoJobsFound"
iconType="alert"
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.js b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.js
index 6f391f9746f2..029ca0475015 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.js
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.js
@@ -7,25 +7,40 @@
/*
* React component for rendering EuiEmptyPrompt when no jobs were found.
*/
-
+import { Link } from 'react-router-dom';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { useMlLink } from '../../../contexts/kibana/use_create_url';
-export const ExplorerNoJobsFound = () => (
-
-
-
- }
- actions={
-
-
-
- }
- data-test-subj="mlNoJobsFound"
- />
-);
+export const ExplorerNoJobsFound = () => {
+ const ADJobsManagementUrl = useMlLink({
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ excludeBasePath: true,
+ });
+ return (
+
+
+
+ }
+ actions={
+
+
+
+
+
+ }
+ data-test-subj="mlNoJobsFound"
+ />
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.test.js b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.test.js
index bcb11cad9674..c9645b787a8e 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.test.js
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_jobs_found/explorer_no_jobs_found.test.js
@@ -8,6 +8,9 @@ import React from 'react';
import { shallow } from 'enzyme';
import { ExplorerNoJobsFound } from './explorer_no_jobs_found';
+jest.mock('../../../contexts/kibana/use_create_url', () => ({
+ useMlLink: jest.fn().mockReturnValue('/jobs'),
+}));
describe('ExplorerNoInfluencersFound', () => {
test('snapshot', () => {
const wrapper = shallow();
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
index 4fb783bfb600..8f03b1903800 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import {
EuiButtonEmpty,
@@ -28,6 +28,10 @@ import { CHART_TYPE } from '../explorer_constants';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MlTooltipComponent } from '../../components/chart_tooltip';
+import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
+import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator';
+import { PLUGIN_ID } from '../../../../common/constants/app';
+import { addItemToRecentlyAccessed } from '../../util/recently_accessed';
const textTooManyBuckets = i18n.translate('xpack.ml.explorer.charts.tooManyBucketsDescription', {
defaultMessage:
@@ -51,7 +55,23 @@ function getChartId(series) {
}
// Wrapper for a single explorer chart
-function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) {
+function ExplorerChartContainer({
+ series,
+ severity,
+ tooManyBuckets,
+ wrapLabel,
+ navigateToApp,
+ mlUrlGenerator,
+}) {
+ const redirectToSingleMetricViewer = useCallback(async () => {
+ const singleMetricViewerLink = await getExploreSeriesLink(mlUrlGenerator, series);
+ addItemToRecentlyAccessed('timeseriesexplorer', series.jobId, singleMetricViewerLink);
+
+ await navigateToApp(PLUGIN_ID, {
+ path: singleMetricViewerLink,
+ });
+ }, [mlUrlGenerator]);
+
const { detectorLabel, entityFields } = series;
const chartType = getChartType(series);
@@ -106,7 +126,7 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel })
iconSide="right"
iconType="visLine"
size="xs"
- onClick={() => window.open(getExploreSeriesLink(series), '_blank')}
+ onClick={redirectToSingleMetricViewer}
>
@@ -150,12 +170,24 @@ function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel })
}
// Flex layout wrapper for all explorer charts
-export const ExplorerChartsContainer = ({
+export const ExplorerChartsContainerUI = ({
chartsPerRow,
seriesToPlot,
severity,
tooManyBuckets,
+ kibana,
}) => {
+ const {
+ services: {
+ application: { navigateToApp },
+
+ share: {
+ urlGenerators: { getUrlGenerator },
+ },
+ },
+ } = kibana;
+ const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
+
// doesn't allow a setting of `columns={1}` when chartsPerRow would be 1.
// If that's the case we trick it doing that with the following settings:
const chartsWidth = chartsPerRow === 1 ? 'calc(100% - 20px)' : 'auto';
@@ -177,9 +209,13 @@ export const ExplorerChartsContainer = ({
severity={severity}
tooManyBuckets={tooManyBuckets}
wrapLabel={wrapLabel}
+ navigateToApp={navigateToApp}
+ mlUrlGenerator={mlUrlGenerator}
/>
))}
);
};
+
+export const ExplorerChartsContainer = withKibana(ExplorerChartsContainerUI);
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js
index 8257ac2b3a70..2da212c8f2f2 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js
@@ -40,6 +40,12 @@ jest.mock('../../services/job_service', () => ({
},
}));
+jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({
+ withKibana: (comp) => {
+ return comp;
+ },
+}));
+
describe('ExplorerChartsContainer', () => {
const mockedGetBBox = { x: 0, y: -11.5, width: 12.1875, height: 14.5 };
const originalGetBBox = SVGElement.prototype.getBBox;
@@ -47,10 +53,22 @@ describe('ExplorerChartsContainer', () => {
beforeEach(() => (SVGElement.prototype.getBBox = () => mockedGetBBox));
afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox));
+ const kibanaContextMock = {
+ services: {
+ application: { navigateToApp: jest.fn() },
+ share: {
+ urlGenerators: { getUrlGenerator: jest.fn() },
+ },
+ },
+ };
test('Minimal Initialization', () => {
const wrapper = shallow(
-
+
);
@@ -71,10 +89,11 @@ describe('ExplorerChartsContainer', () => {
],
chartsPerRow: 1,
tooManyBuckets: false,
+ severity: 10,
};
const wrapper = mount(
-
+
);
@@ -98,10 +117,11 @@ describe('ExplorerChartsContainer', () => {
],
chartsPerRow: 1,
tooManyBuckets: false,
+ severity: 10,
};
const wrapper = mount(
-
+
);
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
index d0d0442dd4ae..85a342838a50 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js
@@ -5,13 +5,20 @@
*/
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useMemo } from 'react';
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useCreateADLinks } from '../../../../components/custom_hooks/use_create_ad_links';
+import { Link } from 'react-router-dom';
+import { useMlKibana } from '../../../../contexts/kibana';
-export function ResultLinks({ jobs }) {
+export function ResultLinks({ jobs, isManagementTable }) {
+ const {
+ services: {
+ http: { basePath },
+ },
+ } = useMlKibana();
const openJobsInSingleMetricViewerText = i18n.translate(
'xpack.ml.jobsList.resultActions.openJobsInSingleMetricViewerText',
{
@@ -37,29 +44,59 @@ export function ResultLinks({ jobs }) {
const singleMetricEnabled = jobs.length === 1 && jobs[0].isSingleMetricViewerJob;
const jobActionsDisabled = jobs.length === 1 && jobs[0].deleting === true;
const { createLinkWithUserDefaults } = useCreateADLinks();
+ const timeSeriesExplorerLink = useMemo(
+ () => createLinkWithUserDefaults('timeseriesexplorer', jobs),
+ [jobs]
+ );
+ const anomalyExplorerLink = useMemo(() => createLinkWithUserDefaults('explorer', jobs), [jobs]);
+
return (
{singleMetricVisible && (
-
+ {isManagementTable ? (
+
+ ) : (
+
+
+
+ )}
)}
-
+ {isManagementTable ? (
+
+ ) : (
+
+
+
+ )}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
index 8f89c4a04918..73b212b97b4c 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
@@ -5,10 +5,10 @@
*/
import React from 'react';
-import { EuiLink } from '@elastic/eui';
import { detectorToString } from '../../../../util/string_utils';
import { formatValues, filterObjects } from './format_values';
import { i18n } from '@kbn/i18n';
+import { Link } from 'react-router-dom';
export function extractJobDetails(job) {
if (Object.keys(job).length === 0) {
@@ -61,7 +61,7 @@ export function extractJobDetails(job) {
if (job.calendars) {
calendars.items = job.calendars.map((c) => [
'',
- {c},
+ {c},
]);
// remove the calendars list from the general section
// so not to show it twice.
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js
index b6157c8694a1..b32070fff73a 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js
@@ -5,8 +5,6 @@
*/
import PropTypes from 'prop-types';
-import rison from 'rison-node';
-
import React, { Component } from 'react';
import {
@@ -30,13 +28,19 @@ import {
getLatestDataOrBucketTimestamp,
isTimeSeriesViewJob,
} from '../../../../../../../common/util/job_utils';
+import { withKibana } from '../../../../../../../../../../src/plugins/kibana_react/public';
+import {
+ ML_APP_URL_GENERATOR,
+ ML_PAGES,
+} from '../../../../../../../common/constants/ml_url_generator';
+import { PLUGIN_ID } from '../../../../../../../common/constants/app';
const MAX_FORECASTS = 500;
/**
* Table component for rendering the lists of forecasts run on an ML job.
*/
-export class ForecastsTable extends Component {
+export class ForecastsTableUI extends Component {
constructor(props) {
super(props);
this.state = {
@@ -78,7 +82,17 @@ export class ForecastsTable extends Component {
}
}
- openSingleMetricView(forecast) {
+ async openSingleMetricView(forecast) {
+ const {
+ services: {
+ application: { navigateToApp },
+
+ share: {
+ urlGenerators: { getUrlGenerator },
+ },
+ },
+ } = this.props.kibana;
+
// Creates the link to the Single Metric Viewer.
// Set the total time range from the start of the job data to the end of the forecast,
const dataCounts = this.props.job.data_counts;
@@ -93,31 +107,7 @@ export class ForecastsTable extends Component {
? new Date(forecast.forecast_end_timestamp).toISOString()
: new Date(resultLatest).toISOString();
- const _g = rison.encode({
- ml: {
- jobIds: [this.props.job.job_id],
- },
- refreshInterval: {
- display: 'Off',
- pause: false,
- value: 0,
- },
- time: {
- from,
- to,
- mode: 'absolute',
- },
- });
-
- const appState = {
- query: {
- query_string: {
- analyze_wildcard: true,
- query: '*',
- },
- },
- };
-
+ let mlTimeSeriesExplorer = {};
if (forecast !== undefined) {
// Set the zoom to show duration before the forecast equal to the length of the forecast.
const forecastDurationMs =
@@ -126,8 +116,7 @@ export class ForecastsTable extends Component {
forecast.forecast_start_timestamp - forecastDurationMs,
jobEarliest
);
-
- appState.mlTimeSeriesExplorer = {
+ mlTimeSeriesExplorer = {
forecastId: forecast.forecast_id,
zoom: {
from: new Date(zoomFrom).toISOString(),
@@ -136,11 +125,39 @@ export class ForecastsTable extends Component {
};
}
- const _a = rison.encode(appState);
-
- const url = `?_g=${_g}&_a=${_a}`;
- addItemToRecentlyAccessed('timeseriesexplorer', this.props.job.job_id, url);
- window.open(`#/timeseriesexplorer${url}`, '_self');
+ const mlUrlGenerator = getUrlGenerator(ML_APP_URL_GENERATOR);
+ const singleMetricViewerForecastLink = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.SINGLE_METRIC_VIEWER,
+ pageState: {
+ timeRange: {
+ from,
+ to,
+ mode: 'absolute',
+ },
+ refreshInterval: {
+ display: 'Off',
+ pause: false,
+ value: 0,
+ },
+ jobIds: [this.props.job.job_id],
+ query: {
+ query_string: {
+ analyze_wildcard: true,
+ query: '*',
+ },
+ },
+ ...mlTimeSeriesExplorer,
+ },
+ excludeBasePath: true,
+ });
+ addItemToRecentlyAccessed(
+ 'timeseriesexplorer',
+ this.props.job.job_id,
+ singleMetricViewerForecastLink
+ );
+ await navigateToApp(PLUGIN_ID, {
+ path: singleMetricViewerForecastLink,
+ });
}
render() {
@@ -322,6 +339,8 @@ export class ForecastsTable extends Component {
);
}
}
-ForecastsTable.propTypes = {
+ForecastsTableUI.propTypes = {
job: PropTypes.object.isRequired,
};
+
+export const ForecastsTable = withKibana(ForecastsTableUI);
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js
index a5469357ba1a..8b5d6009cc61 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_description.js
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { JobGroup } from '../job_group';
-import { getGroupIdsUrl, TAB_IDS } from '../../../../util/get_selected_ids_url';
+import { AnomalyDetectionJobIdLink } from './job_id_link';
export function JobDescription({ job, isManagementTable }) {
return (
@@ -17,11 +17,7 @@ export function JobDescription({ job, isManagementTable }) {
{job.description}
{job.groups.map((group) => {
if (isManagementTable === true) {
- return (
-
-
-
- );
+ return ;
}
return ;
})}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx
new file mode 100644
index 000000000000..0e84619899d7
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/job_id_link.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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 React from 'react';
+import { EuiLink } from '@elastic/eui';
+import { useMlKibana, useMlUrlGenerator } from '../../../../contexts/kibana';
+import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
+import { AnomalyDetectionQueryState } from '../../../../../../common/types/ml_url_generator';
+// @ts-ignore
+import { JobGroup } from '../job_group';
+
+interface JobIdLink {
+ id: string;
+}
+
+interface GroupIdLink {
+ groupId: string;
+ children: string;
+}
+
+type AnomalyDetectionJobIdLinkProps = JobIdLink | GroupIdLink;
+
+function isGroupIdLink(props: JobIdLink | GroupIdLink): props is GroupIdLink {
+ return (props as GroupIdLink).groupId !== undefined;
+}
+export const AnomalyDetectionJobIdLink = (props: AnomalyDetectionJobIdLinkProps) => {
+ const mlUrlGenerator = useMlUrlGenerator();
+ const {
+ services: {
+ application: { navigateToUrl },
+ },
+ } = useMlKibana();
+
+ const redirectToJobsManagementPage = async () => {
+ const pageState: AnomalyDetectionQueryState = {};
+ if (isGroupIdLink(props)) {
+ pageState.groupIds = [props.groupId];
+ } else {
+ pageState.jobId = props.id;
+ }
+ const url = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ pageState,
+ });
+ await navigateToUrl(url);
+ };
+ if (isGroupIdLink(props)) {
+ return (
+ redirectToJobsManagementPage()}>
+
+
+ );
+ } else {
+ return (
+ redirectToJobsManagementPage()}>
+ {props.id}
+
+ );
+ }
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
index fa4ea09b89ff..8bc0057b27d6 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
@@ -14,12 +14,12 @@ import { toLocaleString } from '../../../../util/string_utils';
import { ResultLinks, actionsMenuContent } from '../job_actions';
import { JobDescription } from './job_description';
import { JobIcon } from '../../../../components/job_message_icon';
-import { getJobIdUrl, TAB_IDS } from '../../../../util/get_selected_ids_url';
import { TIME_FORMAT } from '../../../../../../common/constants/time_format';
-import { EuiBadge, EuiBasicTable, EuiButtonIcon, EuiLink, EuiScreenReaderOnly } from '@elastic/eui';
+import { EuiBadge, EuiBasicTable, EuiButtonIcon, EuiScreenReaderOnly } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { AnomalyDetectionJobIdLink } from './job_id_link';
const PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [10, 25, 50];
@@ -71,7 +71,7 @@ export class JobsList extends Component {
return id;
}
- return {id};
+ return ;
}
getPageOfJobs(index, size, sortField, sortDirection) {
@@ -241,7 +241,7 @@ export class JobsList extends Component {
name: i18n.translate('xpack.ml.jobsList.actionsLabel', {
defaultMessage: 'Actions',
}),
- render: (item) => ,
+ render: (item) => ,
},
];
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/new_job_button/new_job_button.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/new_job_button/new_job_button.js
index fdffa8b38ae0..81effe8d3ebe 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/new_job_button/new_job_button.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/new_job_button/new_job_button.js
@@ -11,13 +11,13 @@ import React from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-
-function newJob() {
- window.location.href = `#/jobs/new_job`;
-}
+import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
export function NewJobButton() {
const buttonEnabled = checkPermission('canCreateJob') && mlNodesAvailable();
+ const newJob = useCreateAndNavigateToMlLink(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX);
+
return (
{
const {
@@ -73,7 +74,7 @@ export const CalendarsSelection: FC = () => {
};
const manageCalendarsHref = getUrlForApp(PLUGIN_ID, {
- path: '/settings/calendars_list',
+ path: ML_PAGES.CALENDARS_MANAGE,
});
return (
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx
index 669b8837e74b..021039c06e32 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx
@@ -39,7 +39,10 @@ import { JobSectionTitle, DatafeedSectionTitle } from './components/common';
export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => {
const {
- services: { notifications },
+ services: {
+ notifications,
+ http: { basePath },
+ },
} = useMlKibana();
const navigateToPath = useNavigateToPath();
@@ -108,7 +111,7 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) =>
jobCreator.end,
isSingleMetricJobCreator(jobCreator) === true ? 'timeseriesexplorer' : 'explorer'
);
- window.open(url, '_blank');
+ navigateToPath(`${basePath.get()}/app/ml/${url}`);
}
function clickResetJob() {
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts
index 69df2773f9f8..cedaaa3b5dfa 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts
@@ -4,19 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ApplicationStart } from 'kibana/public';
import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public';
import { mlJobService } from '../../../../services/job_service';
import { loadIndexPatterns, getIndexPatternIdFromName } from '../../../../util/index_utils';
import { CombinedJob } from '../../../../../../common/types/anomaly_detection_jobs';
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../common/constants/new_job';
-export async function preConfiguredJobRedirect(indexPatterns: IndexPatternsContract) {
+export async function preConfiguredJobRedirect(
+ indexPatterns: IndexPatternsContract,
+ basePath: string,
+ navigateToUrl: ApplicationStart['navigateToUrl']
+) {
const { job } = mlJobService.tempJobCloningObjects;
if (job) {
try {
await loadIndexPatterns(indexPatterns);
const redirectUrl = getWizardUrlFromCloningJob(job);
- window.location.href = `#/${redirectUrl}`;
+ await navigateToUrl(`${basePath}/app/ml/${redirectUrl}`);
return Promise.reject();
} catch (error) {
return Promise.resolve();
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx
index be0135ec3f1e..1a91f6d51ed4 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx
@@ -19,6 +19,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useNavigateToPath } from '../../../../contexts/kibana';
+
import { useMlContext } from '../../../../contexts/ml';
import { isSavedSearchSavedObject } from '../../../../../../common/types/kibana';
import { DataRecognizer } from '../../../../components/data_recognizer';
@@ -26,10 +27,15 @@ import { addItemToRecentlyAccessed } from '../../../../util/recently_accessed';
import { timeBasedIndexCheck } from '../../../../util/index_utils';
import { CreateJobLinkCard } from '../../../../components/create_job_link_card';
import { CategorizationIcon } from './categorization_job_icon';
+import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
+import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
export const Page: FC = () => {
const mlContext = useMlContext();
const navigateToPath = useNavigateToPath();
+ const onSelectDifferentIndex = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
+ );
const [recognizerResultsCount, setRecognizerResultsCount] = useState(0);
@@ -193,7 +199,7 @@ export const Page: FC = () => {
defaultMessage="Anomaly detection can only be run over indices which are time based."
/>
-
+
= ({ moduleId, existingGroupIds }) => {
const {
services: { notifications },
} = useMlKibana();
+ const urlGenerator = useMlUrlGenerator();
+
// #region State
const [jobPrefix, setJobPrefix] = useState('');
const [jobs, setJobs] = useState([]);
@@ -185,14 +189,20 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => {
})
);
setKibanaObjects(merge(kibanaObjects, kibanaResponse));
- setResultsUrl(
- mlJobService.createResultsUrl(
- jobsResponse.filter(({ success }) => success).map(({ id }) => id),
- resultTimeRange.start,
- resultTimeRange.end,
- 'explorer'
- )
- );
+
+ const url = await urlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_EXPLORER,
+ pageState: {
+ jobIds: jobsResponse.filter(({ success }) => success).map(({ id }) => id),
+ timeRange: {
+ from: moment(resultTimeRange.start).format(TIME_FORMAT),
+ to: moment(resultTimeRange.end).format(TIME_FORMAT),
+ mode: 'absolute',
+ },
+ },
+ });
+
+ setResultsUrl(url);
const failedJobsCount = jobsResponse.reduce((count, { success }) => {
return success ? count : count + 1;
}, 0);
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts
index e3b0fd4cefe0..97a03fa21035 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts
@@ -6,33 +6,40 @@
import { i18n } from '@kbn/i18n';
import { getToastNotifications, getSavedObjectsClient } from '../../../util/dependency_cache';
-import { mlJobService } from '../../../services/job_service';
import { ml } from '../../../services/ml_api_service';
import { KibanaObjects } from './page';
+import { NavigateToPath } from '../../../contexts/kibana';
+import { CreateLinkWithUserDefaults } from '../../../components/custom_hooks/use_create_ad_links';
/**
* Checks whether the jobs in a data recognizer module have been created.
* Redirects to the Anomaly Explorer to view the jobs if they have been created,
* or the recognizer job wizard for the module if not.
*/
-export function checkViewOrCreateJobs(moduleId: string, indexPatternId: string): Promise {
+export function checkViewOrCreateJobs(
+ moduleId: string,
+ indexPatternId: string,
+ createLinkWithUserDefaults: CreateLinkWithUserDefaults,
+ navigateToPath: NavigateToPath
+): Promise {
return new Promise((resolve, reject) => {
// Load the module, and check if the job(s) in the module have been created.
// If so, load the jobs in the Anomaly Explorer.
// Otherwise open the data recognizer wizard for the module.
// Always want to call reject() so as not to load original page.
ml.dataRecognizerModuleJobsExist({ moduleId })
- .then((resp: any) => {
+ .then(async (resp: any) => {
if (resp.jobsExist === true) {
- const resultsPageUrl = mlJobService.createResultsUrlForJobs(resp.jobs, 'explorer');
- window.location.href = resultsPageUrl;
+ // also honor user's time filter setting in Advanced Settings
+ const url = createLinkWithUserDefaults('explorer', resp.jobs);
+ await navigateToPath(url);
reject();
} else {
- window.location.href = `#/jobs/new_job/recognize?id=${moduleId}&index=${indexPatternId}`;
+ await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${indexPatternId}`);
reject();
}
})
- .catch((err: Error) => {
+ .catch(async (err: Error) => {
// eslint-disable-next-line no-console
console.error(`Error checking whether jobs in module ${moduleId} exists`, err);
const toastNotifications = getToastNotifications();
@@ -46,8 +53,7 @@ export function checkViewOrCreateJobs(moduleId: string, indexPatternId: string):
'An error occurred trying to check whether the jobs in the module have been created.',
}),
});
-
- window.location.href = '#/jobs';
+ await navigateToPath(`/jobs`);
reject();
});
});
diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx
index 0af6030df28b..9c9096dfdfc2 100644
--- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx
+++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx
@@ -31,6 +31,7 @@ import { getDocLinks } from '../../../../util/dependency_cache';
import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_view/index';
import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list';
import { AccessDeniedPage } from '../access_denied_page';
+import { SharePluginStart } from '../../../../../../../../../src/plugins/share/public';
interface Tab {
'data-test-subj': string;
@@ -75,8 +76,9 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] {
export const JobsListPage: FC<{
coreStart: CoreStart;
+ share: SharePluginStart;
history: ManagementAppMountParams['history'];
-}> = ({ coreStart, history }) => {
+}> = ({ coreStart, share, history }) => {
const [initialized, setInitialized] = useState(false);
const [accessDenied, setAccessDenied] = useState(false);
const [isMlEnabledInSpace, setIsMlEnabledInSpace] = useState(false);
@@ -136,7 +138,7 @@ export const JobsListPage: FC<{
return (
-
+
{
- ReactDOM.render(React.createElement(JobsListPage, { coreStart, history }), element);
+ ReactDOM.render(React.createElement(JobsListPage, { coreStart, history, share }), element);
return () => {
unmountComponentAtNode(element);
clearCache();
@@ -30,7 +32,7 @@ export async function mountApp(
core: CoreSetup,
params: ManagementAppMountParams
) {
- const [coreStart] = await core.getStartServices();
+ const [coreStart, pluginsStart] = await core.getStartServices();
setDependencyCache({
docLinks: coreStart.docLinks!,
@@ -41,5 +43,5 @@ export async function mountApp(
params.setBreadcrumbs(getJobsListBreadcrumbs());
- return renderApp(params.element, params.history, coreStart);
+ return renderApp(params.element, params.history, coreStart, pluginsStart.share);
}
diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
index 1792999eee4c..d0cfd16d7562 100644
--- a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
+++ b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
@@ -9,7 +9,7 @@ import { ml } from '../services/ml_api_service';
let mlNodeCount: number = 0;
let userHasPermissionToViewMlNodeCount: boolean = false;
-export async function checkMlNodesAvailable() {
+export async function checkMlNodesAvailable(redirectToJobsManagementPage: () => Promise) {
try {
const nodes = await getMlNodeCount();
if (nodes.count !== undefined && nodes.count > 0) {
@@ -20,7 +20,7 @@ export async function checkMlNodesAvailable() {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
- window.location.href = '#/jobs';
+ await redirectToJobsManagementPage();
Promise.reject();
}
}
diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
index 395a570083c0..4f0cbc0adddf 100644
--- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/actions.tsx
@@ -4,30 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, FC } from 'react';
+import React, { FC, useMemo } from 'react';
import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { useNavigateToPath } from '../../../contexts/kibana';
+import { Link } from 'react-router-dom';
+import { useMlLink } from '../../../contexts/kibana';
import { getAnalysisType } from '../../../data_frame_analytics/common/analytics';
-import {
- getResultsUrl,
- DataFrameAnalyticsListRow,
-} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
+import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { getViewLinkStatus } from '../../../data_frame_analytics/pages/analytics_management/components/action_view/get_view_link_status';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
interface Props {
item: DataFrameAnalyticsListRow;
}
export const ViewLink: FC = ({ item }) => {
- const navigateToPath = useNavigateToPath();
-
- const clickHandler = useCallback(() => {
- const analysisType = getAnalysisType(item.config.analysis);
- navigateToPath(getResultsUrl(item.id, analysisType));
- }, []);
-
const { disabled, tooltipContent } = getViewLinkStatus(item);
const viewJobResultsButtonText = i18n.translate(
@@ -38,23 +31,34 @@ export const ViewLink: FC = ({ item }) => {
);
const tooltipText = disabled === false ? viewJobResultsButtonText : tooltipContent;
+ const analysisType = useMemo(() => getAnalysisType(item.config.analysis), [item]);
+
+ const viewAnalyticsResultsLink = useMlLink({
+ page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
+ pageState: {
+ jobId: item.id,
+ analysisType: analysisType as DataFrameAnalysisConfigType,
+ },
+ excludeBasePath: true,
+ });
return (
-
- {i18n.translate('xpack.ml.overview.analytics.viewActionName', {
- defaultMessage: 'View',
- })}
-
+
+
+ {i18n.translate('xpack.ml.overview.analytics.viewActionName', {
+ defaultMessage: 'View',
+ })}
+
+
);
};
diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
index be8038cc5049..4d810c47415a 100644
--- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
@@ -23,6 +23,8 @@ import { AnalyticsTable } from './table';
import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service';
import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar';
+import { useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
interface Props {
jobCreationDisabled: boolean;
@@ -35,6 +37,16 @@ export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => {
const [errorMessage, setErrorMessage] = useState(undefined);
const [isInitialized, setIsInitialized] = useState(false);
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
+ const redirectToDataFrameAnalyticsManagementPage = async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
+ });
+ await navigateToPath(path, true);
+ };
+
const getAnalytics = getAnalyticsFactory(
setAnalytics,
setAnalyticsStats,
@@ -75,7 +87,6 @@ export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => {
{isInitialized === false && (
)}
- Â Â Â Â
{errorMessage === undefined && isInitialized === true && analytics.length === 0 && (
= ({ jobCreationDisabled }) => {
}
actions={
= ({ jobCreationDisabled }) => {
)}
{isInitialized === true && analytics.length > 0 && (
<>
+
@@ -136,7 +148,7 @@ export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => {
defaultMessage: 'Refresh',
})}
-
+
{i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
defaultMessage: 'Manage jobs',
})}
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
index a71141d0356d..dfba7c965126 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx
@@ -7,6 +7,7 @@
import React, { FC } from 'react';
import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { Link } from 'react-router-dom';
import { MlSummaryJobs } from '../../../../../common/types/anomaly_detection_jobs';
import { useCreateADLinks } from '../../../components/custom_hooks/use_create_ad_links';
@@ -26,19 +27,20 @@ export const ExplorerLink: FC = ({ jobsList }) => {
return (
-
- {i18n.translate('xpack.ml.overview.anomalyDetection.viewActionName', {
- defaultMessage: 'View',
- })}
-
+
+
+ {i18n.translate('xpack.ml.overview.anomalyDetection.viewActionName', {
+ defaultMessage: 'View',
+ })}
+
+
);
};
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
index 0bfd2c2e4923..1cb6bab7fd76 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
@@ -16,12 +16,13 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import moment from 'moment';
-import { useMlKibana } from '../../../contexts/kibana';
+import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
import { AnomalyDetectionTable } from './table';
import { ml } from '../../../services/ml_api_service';
import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils';
import { Dictionary } from '../../../../../common/types/common';
import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
export type GroupsDictionary = Dictionary;
@@ -39,8 +40,6 @@ type MaxScoresByGroup = Dictionary<{
index?: number;
}>;
-const createJobLink = '#/jobs/new_job/step/index_or_search';
-
function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup {
const anomalyScores: MaxScoresByGroup = {};
groups.forEach((group) => {
@@ -58,6 +57,23 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => {
const {
services: { notifications },
} = useMlKibana();
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
+ const redirectToJobsManagementPage = async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ });
+ await navigateToPath(path, true);
+ };
+
+ const redirectToCreateJobSelectIndexPage = async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX,
+ });
+ await navigateToPath(path, true);
+ };
+
const [isLoading, setIsLoading] = useState(false);
const [groups, setGroups] = useState({});
const [groupsCount, setGroupsCount] = useState(0);
@@ -157,7 +173,7 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => {
return (
{typeof errorMessage !== 'undefined' && errorDisplay}
- {isLoading && }Â Â Â
+ {isLoading && }
{isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && (
= ({ jobCreationDisabled }) => {
actions={
= ({ jobCreationDisabled }) => {
defaultMessage: 'Refresh',
})}
-
+
{i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
defaultMessage: 'Manage jobs',
})}
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
index 945116b0534b..8515431d49b1 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
@@ -88,7 +88,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData
{i18n.translate('xpack.ml.overview.anomalyDetection.tableMaxScore', {
defaultMessage: 'Max anomaly score',
- })}{' '}
+ })}
@@ -203,6 +203,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData
return (
+
diff --git a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts
index d0a4f999af75..398ec5b4759d 100644
--- a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts
+++ b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts
@@ -54,6 +54,20 @@ export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
href: '/jobs/new_job',
});
+export const CALENDAR_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
+ text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
+ defaultMessage: 'Calendar management',
+ }),
+ href: '/settings/calendars_list',
+});
+
+export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({
+ text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsLabel', {
+ defaultMessage: 'Filter lists',
+ }),
+ href: '/settings/filter_lists',
+});
+
const breadcrumbs = {
ML_BREADCRUMB,
SETTINGS_BREADCRUMB,
@@ -61,6 +75,8 @@ const breadcrumbs = {
DATA_FRAME_ANALYTICS_BREADCRUMB,
DATA_VISUALIZER_BREADCRUMB,
CREATE_JOB_BREADCRUMB,
+ CALENDAR_MANAGEMENT_BREADCRUMB,
+ FILTER_LISTS_BREADCRUMB,
};
type Breadcrumb = keyof typeof breadcrumbs;
@@ -76,10 +92,12 @@ export const breadcrumbOnClickFactory = (
export const getBreadcrumbWithUrlForApp = (
breadcrumbName: Breadcrumb,
- navigateToPath: NavigateToPath
+ navigateToPath: NavigateToPath,
+ basePath: string
): EuiBreadcrumb => {
return {
- ...breadcrumbs[breadcrumbName],
+ text: breadcrumbs[breadcrumbName].text,
+ href: `${basePath}/app/ml${breadcrumbs[breadcrumbName].href}`,
onClick: breadcrumbOnClickFactory(breadcrumbs[breadcrumbName].href, navigateToPath),
};
};
diff --git a/x-pack/plugins/ml/public/application/routing/resolvers.ts b/x-pack/plugins/ml/public/application/routing/resolvers.ts
index 958221df8a63..9cebb67166a6 100644
--- a/x-pack/plugins/ml/public/application/routing/resolvers.ts
+++ b/x-pack/plugins/ml/public/application/routing/resolvers.ts
@@ -21,13 +21,17 @@ export interface ResolverResults {
interface BasicResolverDependencies {
indexPatterns: IndexPatternsContract;
+ redirectToMlAccessDeniedPage: () => Promise;
}
-export const basicResolvers = ({ indexPatterns }: BasicResolverDependencies): Resolvers => ({
+export const basicResolvers = ({
+ indexPatterns,
+ redirectToMlAccessDeniedPage,
+}: BasicResolverDependencies): Resolvers => ({
checkFullLicense,
getMlNodeCount,
loadMlServerInfo,
loadIndexPatterns: () => loadIndexPatterns(indexPatterns),
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
loadSavedSearches,
});
diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx
index 22a17c4ea089..7cb3a2f07c2e 100644
--- a/x-pack/plugins/ml/public/application/routing/router.tsx
+++ b/x-pack/plugins/ml/public/application/routing/router.tsx
@@ -12,7 +12,7 @@ import { AppMountParameters, IUiSettingsClient, ChromeStart } from 'kibana/publi
import { ChromeBreadcrumb } from 'kibana/public';
import { IndexPatternsContract } from 'src/plugins/data/public';
-import { useNavigateToPath } from '../contexts/kibana';
+import { useMlKibana, useNavigateToPath } from '../contexts/kibana';
import { MlContext, MlContextValue } from '../contexts/ml';
import { UrlStateProvider } from '../util/url_state';
@@ -39,6 +39,7 @@ interface PageDependencies {
history: AppMountParameters['history'];
indexPatterns: IndexPatternsContract;
setBreadcrumbs: ChromeStart['setBreadcrumbs'];
+ redirectToMlAccessDeniedPage: () => Promise;
}
export const PageLoader: FC<{ context: MlContextValue }> = ({ context, children }) => {
@@ -75,10 +76,16 @@ const MlRoutes: FC<{
pageDeps: PageDependencies;
}> = ({ pageDeps }) => {
const navigateToPath = useNavigateToPath();
+ const {
+ services: {
+ http: { basePath },
+ },
+ } = useMlKibana();
+
return (
<>
{Object.entries(routes).map(([name, routeFactory]) => {
- const route = routeFactory(navigateToPath);
+ const route = routeFactory(navigateToPath, basePath.get());
return (
({
+export const analyticsJobsCreationRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/data_frame_analytics/new_job',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameManagementLabel', {
defaultMessage: 'Data Frame Analytics',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
index 47cc002ab4d8..f9f2ebe48f4a 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx
@@ -10,21 +10,25 @@ import { decode } from 'rison-node';
import { i18n } from '@kbn/i18n';
-import { NavigateToPath } from '../../../contexts/kibana';
+import { NavigateToPath, useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
import { MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
-import { ANALYSIS_CONFIG_TYPE } from '../../../data_frame_analytics/common/analytics';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics';
-export const analyticsJobExplorationRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const analyticsJobExplorationRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/data_frame_analytics/exploration',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameExplorationLabel', {
defaultMessage: 'Exploration',
@@ -38,16 +42,31 @@ const PageWrapper: FC = ({ location, deps }) => {
const { context } = useResolver('', undefined, deps.config, basicResolvers(deps));
const { _g }: Record = parse(location.search, { sort: false });
+ const urlGenerator = useMlUrlGenerator();
+ const {
+ services: {
+ application: { navigateToUrl },
+ },
+ } = useMlKibana();
+
+ const redirectToAnalyticsManagementPage = async () => {
+ const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE });
+ await navigateToUrl(url);
+ };
+
let globalState: any = null;
try {
globalState = decode(_g);
} catch (error) {
// eslint-disable-next-line no-console
- console.error('Could not parse global state');
- window.location.href = '#data_frame_analytics';
+ console.error(
+ 'Could not parse global state. Redirecting to Data Frame Analytics Management Page.'
+ );
+ redirectToAnalyticsManagementPage();
+ return <>>;
}
const jobId: string = globalState.ml.jobId;
- const analysisType: ANALYSIS_CONFIG_TYPE = globalState.ml.analysisType;
+ const analysisType: DataFrameAnalysisConfigType = globalState.ml.analysisType;
return (
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx
index b6ef9ea81b4b..80706a82121d 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx
@@ -15,12 +15,15 @@ import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_management';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const analyticsJobsListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const analyticsJobsListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/data_frame_analytics',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameListLabel', {
defaultMessage: 'Job Management',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
index 7bf7784d1b55..b1fd6e93a744 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx
@@ -15,12 +15,15 @@ import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_management';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const modelsListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const modelsListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/data_frame_analytics/models',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.modelsListLabel', {
defaultMessage: 'Model Management',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx
index efe5c3cba04a..f40b754a23cc 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx
@@ -21,19 +21,25 @@ import { checkBasicLicense } from '../../../license';
import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const selectorRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const selectorRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/datavisualizer',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath),
],
});
const PageWrapper: FC = ({ location, deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver(undefined, undefined, deps.config, {
checkBasicLicense,
- checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
+ checkFindFileStructurePrivilege: () =>
+ checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
return (
diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx
index 485af52c45a5..837616a8a76d 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx
@@ -24,12 +24,15 @@ import { loadIndexPatterns } from '../../../util/index_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const fileBasedRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const fileBasedRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/filedatavisualizer',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataVisualizer.fileBasedLabel', {
defaultMessage: 'File',
@@ -40,10 +43,13 @@ export const fileBasedRouteFactory = (navigateToPath: NavigateToPath): MlRoute =
});
const PageWrapper: FC = ({ location, deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver('', undefined, deps.config, {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
- checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
+ checkFindFileStructurePrivilege: () =>
+ checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
return (
diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx
index 358b8773e346..e3d0e5050fca 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx
@@ -20,13 +20,18 @@ import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_ca
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
-export const indexBasedRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const indexBasedRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/datavisualizer',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.indexLabel', {
defaultMessage: 'Index',
@@ -37,12 +42,17 @@ export const indexBasedRouteFactory = (navigateToPath: NavigateToPath): MlRoute
});
const PageWrapper: FC = ({ location, deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
+
const { index, savedSearchId }: Record = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
- checkMlNodesAvailable,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
+ checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage),
});
return (
diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
index 30b9bc2af219..00d64a2f1bd1 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -35,12 +35,15 @@ import { useTimefilter } from '../../contexts/kibana';
import { isViewBySwimLaneData } from '../../explorer/swimlane_container';
import { JOB_ID } from '../../../../common/constants/anomalies';
-export const explorerRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const explorerRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/explorer',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.anomalyExplorerLabel', {
defaultMessage: 'Anomaly Explorer',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx
index 38a7900916ba..2863e59508e3 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx
@@ -20,12 +20,12 @@ import { JobsPage } from '../../jobs/jobs_list';
import { useTimefilter } from '../../contexts/kibana';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
-export const jobListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
path: '/jobs',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.jobManagementLabel', {
defaultMessage: 'Job Management',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx
index d8605c4cc911..0ef3b384dcf5 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx
@@ -8,7 +8,7 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { NavigateToPath } from '../../../contexts/kibana';
+import { NavigateToPath, useMlKibana } from '../../../contexts/kibana';
import { MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
@@ -19,6 +19,8 @@ import { checkBasicLicense } from '../../../license';
import { loadIndexPatterns } from '../../../util/index_utils';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
+import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
enum MODE {
NEW_JOB,
@@ -30,9 +32,9 @@ interface IndexOrSearchPageProps extends PageProps {
mode: MODE;
}
-const getBreadcrumbs = (navigateToPath: NavigateToPath) => [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
+const getBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabel', {
defaultMessage: 'Create job',
@@ -41,7 +43,10 @@ const getBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-export const indexOrSearchRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const indexOrSearchRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/step/index_or_search',
render: (props, deps) => (
),
- breadcrumbs: getBreadcrumbs(navigateToPath),
+ breadcrumbs: getBreadcrumbs(navigateToPath, basePath),
});
-export const dataVizIndexOrSearchRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const dataVizIndexOrSearchRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/datavisualizer_index_select',
render: (props, deps) => (
),
- breadcrumbs: getBreadcrumbs(navigateToPath),
+ breadcrumbs: getBreadcrumbs(navigateToPath, basePath),
});
const PageWrapper: FC = ({ nextStepPath, deps, mode }) => {
+ const {
+ services: {
+ http: { basePath },
+ application: { navigateToUrl },
+ },
+ } = useMlKibana();
+ const { redirectToMlAccessDeniedPage } = deps;
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
+
const newJobResolvers = {
...basicResolvers(deps),
- preConfiguredJobRedirect: () => preConfiguredJobRedirect(deps.indexPatterns),
+ preConfiguredJobRedirect: () =>
+ preConfiguredJobRedirect(deps.indexPatterns, basePath.get(), navigateToUrl),
};
const dataVizResolvers = {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
- checkMlNodesAvailable,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
+ checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage),
};
const { context } = useResolver(
diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx
index b8ab29d40fa1..543e01fbd326 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx
@@ -17,12 +17,12 @@ import { basicResolvers } from '../../resolvers';
import { Page } from '../../../jobs/new_job/pages/job_type';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const jobTypeRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
path: '/jobs/new_job/step/job_type',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectJobType', {
defaultMessage: 'Create job',
diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx
index 6be58828ee1a..654d7184cfcf 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx
@@ -9,7 +9,7 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
-import { NavigateToPath } from '../../../contexts/kibana';
+import { NavigateToPath, useNavigateToPath } from '../../../contexts/kibana';
import { MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
@@ -18,14 +18,18 @@ import { Page } from '../../../jobs/new_job/recognize';
import { checkViewOrCreateJobs } from '../../../jobs/new_job/recognize/resolvers';
import { mlJobService } from '../../../services/job_service';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { useCreateADLinks } from '../../../components/custom_hooks/use_create_ad_links';
-export const recognizeRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const recognizeRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/recognize',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('CREATE_JOB_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('CREATE_JOB_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabelRecognize', {
defaultMessage: 'Recognized index',
@@ -60,10 +64,14 @@ const CheckViewOrCreateWrapper: FC = ({ location, deps }) => {
const { id: moduleId, index: indexPatternId }: Record = parse(location.search, {
sort: false,
});
+ const { createLinkWithUserDefaults } = useCreateADLinks();
+
+ const navigateToPath = useNavigateToPath();
// the single resolver checkViewOrCreateJobs redirects only. so will always reject
useResolver(undefined, undefined, deps.config, {
- checkViewOrCreateJobs: () => checkViewOrCreateJobs(moduleId, indexPatternId),
+ checkViewOrCreateJobs: () =>
+ checkViewOrCreateJobs(moduleId, indexPatternId, createLinkWithUserDefaults, navigateToPath),
});
return null;
};
diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx
index 35085fd55757..8a82a9a8dbc4 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx
@@ -19,19 +19,21 @@ import { mlJobService } from '../../../services/job_service';
import { loadNewJobCapabilities } from '../../../services/new_job_capabilities_service';
import { checkCreateJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
interface WizardPageProps extends PageProps {
jobType: JOB_TYPE;
}
-const getBaseBreadcrumbs = (navigateToPath: NavigateToPath) => [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('CREATE_JOB_BREADCRUMB', navigateToPath),
+const getBaseBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('CREATE_JOB_BREADCRUMB', navigateToPath, basePath),
];
-const getSingleMetricBreadcrumbs = (navigateToPath: NavigateToPath) => [
- ...getBaseBreadcrumbs(navigateToPath),
+const getSingleMetricBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ ...getBaseBreadcrumbs(navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.singleMetricLabel', {
defaultMessage: 'Single metric',
@@ -40,8 +42,8 @@ const getSingleMetricBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-const getMultiMetricBreadcrumbs = (navigateToPath: NavigateToPath) => [
- ...getBaseBreadcrumbs(navigateToPath),
+const getMultiMetricBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ ...getBaseBreadcrumbs(navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.multiMetricLabel', {
defaultMessage: 'Multi-metric',
@@ -50,8 +52,8 @@ const getMultiMetricBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-const getPopulationBreadcrumbs = (navigateToPath: NavigateToPath) => [
- ...getBaseBreadcrumbs(navigateToPath),
+const getPopulationBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ ...getBaseBreadcrumbs(navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.populationLabel', {
defaultMessage: 'Population',
@@ -60,8 +62,8 @@ const getPopulationBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-const getAdvancedBreadcrumbs = (navigateToPath: NavigateToPath) => [
- ...getBaseBreadcrumbs(navigateToPath),
+const getAdvancedBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ ...getBaseBreadcrumbs(navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.advancedConfigurationLabel', {
defaultMessage: 'Advanced configuration',
@@ -70,8 +72,8 @@ const getAdvancedBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-const getCategorizationBreadcrumbs = (navigateToPath: NavigateToPath) => [
- ...getBaseBreadcrumbs(navigateToPath),
+const getCategorizationBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
+ ...getBaseBreadcrumbs(navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.jobsBreadcrumbs.categorizationLabel', {
defaultMessage: 'Categorization',
@@ -80,41 +82,60 @@ const getCategorizationBreadcrumbs = (navigateToPath: NavigateToPath) => [
},
];
-export const singleMetricRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const singleMetricRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/single_metric',
render: (props, deps) => ,
- breadcrumbs: getSingleMetricBreadcrumbs(navigateToPath),
+ breadcrumbs: getSingleMetricBreadcrumbs(navigateToPath, basePath),
});
-export const multiMetricRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const multiMetricRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/multi_metric',
render: (props, deps) => ,
- breadcrumbs: getMultiMetricBreadcrumbs(navigateToPath),
+ breadcrumbs: getMultiMetricBreadcrumbs(navigateToPath, basePath),
});
-export const populationRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const populationRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/population',
render: (props, deps) => ,
- breadcrumbs: getPopulationBreadcrumbs(navigateToPath),
+ breadcrumbs: getPopulationBreadcrumbs(navigateToPath, basePath),
});
-export const advancedRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const advancedRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/advanced',
render: (props, deps) => ,
- breadcrumbs: getAdvancedBreadcrumbs(navigateToPath),
+ breadcrumbs: getAdvancedBreadcrumbs(navigateToPath, basePath),
});
-export const categorizationRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const categorizationRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/jobs/new_job/categorization',
render: (props, deps) => ,
- breadcrumbs: getCategorizationBreadcrumbs(navigateToPath),
+ breadcrumbs: getCategorizationBreadcrumbs(navigateToPath, basePath),
});
const PageWrapper: FC = ({ location, jobType, deps }) => {
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
+
const { index, savedSearchId }: Record = parse(location.search, { sort: false });
const { context, results } = useResolver(index, savedSearchId, deps.config, {
...basicResolvers(deps),
- privileges: checkCreateJobsCapabilitiesResolver,
+ privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage),
jobCaps: () => loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
});
diff --git a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx
index 174e9804b968..0e07b0edfbe5 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx
@@ -22,11 +22,14 @@ import { loadMlServerInfo } from '../../services/ml_server_info';
import { useTimefilter } from '../../contexts/kibana';
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../breadcrumbs';
-export const overviewRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const overviewRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/overview',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.overview.overviewLabel', {
defaultMessage: 'Overview',
@@ -37,9 +40,11 @@ export const overviewRouteFactory = (navigateToPath: NavigateToPath): MlRoute =>
});
const PageWrapper: FC = ({ deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
loadMlServerInfo,
});
@@ -52,7 +57,7 @@ const PageWrapper: FC = ({ deps }) => {
);
};
-export const appRootRouteFactory = (): MlRoute => ({
+export const appRootRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
path: '/',
render: () => ,
breadcrumbs: [],
diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx
index f2ae57f1ec96..246097123961 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx
@@ -10,7 +10,6 @@
*/
import React, { FC } from 'react';
-import { i18n } from '@kbn/i18n';
import { NavigateToPath } from '../../../contexts/kibana';
@@ -25,27 +24,27 @@ import {
} from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { CalendarsList } from '../../../settings/calendars';
-import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const calendarListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const calendarListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/calendars_list',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
- {
- text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
- defaultMessage: 'Calendar management',
- }),
- onClick: breadcrumbOnClickFactory('/settings/calendars_list', navigateToPath),
- },
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('CALENDAR_MANAGEMENT_BREADCRUMB', navigateToPath, basePath),
],
});
const PageWrapper: FC = ({ deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx
index a5c30e1eaaac..4e0a8340590a 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx
@@ -26,6 +26,8 @@ import {
import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { NewCalendar } from '../../../settings/calendars';
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
enum MODE {
NEW,
@@ -36,12 +38,16 @@ interface NewCalendarPageProps extends PageProps {
mode: MODE;
}
-export const newCalendarRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const newCalendarRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/calendars_list/new_calendar',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('CALENDAR_MANAGEMENT_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagement.createLabel', {
defaultMessage: 'Create',
@@ -51,12 +57,16 @@ export const newCalendarRouteFactory = (navigateToPath: NavigateToPath): MlRoute
],
});
-export const editCalendarRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const editCalendarRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/calendars_list/edit_calendar/:calendarId',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('CALENDAR_MANAGEMENT_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagement.editLabel', {
defaultMessage: 'Edit',
@@ -72,11 +82,15 @@ const PageWrapper: FC = ({ location, mode, deps }) => {
const pathMatch: string[] | null = location.pathname.match(/.+\/(.+)$/);
calendarId = pathMatch && pathMatch.length > 1 ? pathMatch[1] : undefined;
}
+ const { redirectToMlAccessDeniedPage } = deps;
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
- checkMlNodesAvailable,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
+ checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage),
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx
index d734e18d72ba..4e39cfce82e3 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx
@@ -10,7 +10,6 @@
*/
import React, { FC } from 'react';
-import { i18n } from '@kbn/i18n';
import { NavigateToPath } from '../../../contexts/kibana';
@@ -26,27 +25,27 @@ import {
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { FilterLists } from '../../../settings/filter_lists';
-import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const filterListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const filterListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/filter_lists',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
- {
- text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsLabel', {
- defaultMessage: 'Filter lists',
- }),
- onClick: breadcrumbOnClickFactory('/settings/filter_lists', navigateToPath),
- },
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('FILTER_LISTS_BREADCRUMB', navigateToPath, basePath),
],
});
const PageWrapper: FC = ({ deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx
index c6f17bc7f6f6..5fe56b024e41 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx
@@ -27,6 +27,8 @@ import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { EditFilterList } from '../../../settings/filter_lists';
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
+import { useCreateAndNavigateToMlLink } from '../../../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
enum MODE {
NEW,
@@ -37,12 +39,17 @@ interface NewFilterPageProps extends PageProps {
mode: MODE;
}
-export const newFilterListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const newFilterListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/filter_lists/new_filter_list',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('FILTER_LISTS_BREADCRUMB', navigateToPath, basePath),
+
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterLists.createLabel', {
defaultMessage: 'Create',
@@ -52,12 +59,16 @@ export const newFilterListRouteFactory = (navigateToPath: NavigateToPath): MlRou
],
});
-export const editFilterListRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const editFilterListRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings/filter_lists/edit_filter_list/:filterId',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('FILTER_LISTS_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterLists.editLabel', {
defaultMessage: 'Edit',
@@ -73,11 +84,15 @@ const PageWrapper: FC = ({ location, mode, deps }) => {
const pathMatch: string[] | null = location.pathname.match(/.+\/(.+)$/);
filterId = pathMatch && pathMatch.length > 1 ? pathMatch[1] : undefined;
}
+ const { redirectToMlAccessDeniedPage } = deps;
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
- checkMlNodesAvailable,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
+ checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage),
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx
index 3f4b26985146..3159c2ae8816 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx
@@ -26,19 +26,24 @@ import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { AnomalyDetectionSettingsContext, Settings } from '../../../settings';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
-export const settingsRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const settingsRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/settings',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
],
});
const PageWrapper: FC = ({ deps }) => {
+ const { redirectToMlAccessDeniedPage } = deps;
+
const { context } = useResolver(undefined, undefined, deps.config, {
checkFullLicense,
- checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
+ checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx
index 11ec074bac1d..b60a26556045 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx
@@ -19,6 +19,11 @@ jest.mock('../../contexts/kibana/kibana_context', () => {
useMlKibana: () => {
return {
services: {
+ chrome: { docTitle: { change: jest.fn() } },
+ application: { getUrlForApp: jest.fn(), navigateToUrl: jest.fn() },
+ share: {
+ urlGenerators: { getUrlGenerator: jest.fn() },
+ },
uiSettings: { get: jest.fn() },
data: {
query: {
diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
index 817c97541599..03588872d6be 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
@@ -39,12 +39,15 @@ import { basicResolvers } from '../resolvers';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { useTimefilter } from '../../contexts/kibana';
-export const timeSeriesExplorerRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
+export const timeSeriesExplorerRouteFactory = (
+ navigateToPath: NavigateToPath,
+ basePath: string
+): MlRoute => ({
path: '/timeseriesexplorer',
render: (props, deps) => ,
breadcrumbs: [
- getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
- getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath),
+ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
+ getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', {
defaultMessage: 'Single Metric Viewer',
diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts
index 4967e3a684a6..e4cd90145bee 100644
--- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts
+++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts
@@ -16,6 +16,8 @@ import { createSearchItems } from '../jobs/new_job/utils/new_job_utils';
import { ResolverResults, Resolvers } from './resolvers';
import { MlContextValue } from '../contexts/ml';
import { useNotifications } from '../contexts/kibana';
+import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../common/constants/ml_url_generator';
export const useResolver = (
indexPatternId: string | undefined,
@@ -34,6 +36,9 @@ export const useResolver = (
const [context, setContext] = useState(null);
const [results, setResults] = useState(tempResults);
+ const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
+ ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
+ );
useEffect(() => {
(async () => {
@@ -73,7 +78,7 @@ export const useResolver = (
defaultMessage: 'An error has occurred',
}),
});
- window.location.href = '#/';
+ await redirectToJobsManagementPage();
}
} else {
setContext({});
diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js
index dfa1b5f4e68c..ea97492ae0f5 100644
--- a/x-pack/plugins/ml/public/application/services/job_service.js
+++ b/x-pack/plugins/ml/public/application/services/job_service.js
@@ -797,7 +797,6 @@ function createResultsUrl(jobIds, start, end, resultsPage, mode = 'absolute') {
let path = '';
if (resultsPage !== undefined) {
- path += '#/';
path += resultsPage;
}
diff --git a/x-pack/plugins/ml/public/application/settings/anomaly_detection_settings.tsx b/x-pack/plugins/ml/public/application/settings/anomaly_detection_settings.tsx
index 16d7e1605263..57caa56b2f10 100644
--- a/x-pack/plugins/ml/public/application/settings/anomaly_detection_settings.tsx
+++ b/x-pack/plugins/ml/public/application/settings/anomaly_detection_settings.tsx
@@ -25,6 +25,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context';
import { useNotifications } from '../contexts/kibana';
import { ml } from '../services/ml_api_service';
+import { ML_PAGES } from '../../../common/constants/ml_url_generator';
+import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url';
export const AnomalyDetectionSettings: FC = () => {
const [calendarsCount, setCalendarsCount] = useState(0);
@@ -35,6 +37,10 @@ export const AnomalyDetectionSettings: FC = () => {
);
const { toasts } = useNotifications();
+ const redirectToCalendarList = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_MANAGE);
+ const redirectToNewCalendarPage = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_NEW);
+ const redirectToFilterLists = useCreateAndNavigateToMlLink(ML_PAGES.FILTER_LISTS_MANAGE);
+ const redirectToNewFilterListPage = useCreateAndNavigateToMlLink(ML_PAGES.FILTER_LISTS_NEW);
useEffect(() => {
loadSummaryStats();
@@ -126,7 +132,7 @@ export const AnomalyDetectionSettings: FC = () => {
flush="left"
size="l"
color="primary"
- href="#/settings/calendars_list"
+ onClick={redirectToCalendarList}
isDisabled={canGetCalendars === false}
>
{
flush="left"
size="l"
color="primary"
- href="#/settings/calendars_list/new_calendar"
+ onClick={redirectToNewCalendarPage}
isDisabled={canCreateCalendar === false}
>
{
@@ -194,7 +200,7 @@ export const AnomalyDetectionSettings: FC = () => {
flush="left"
size="l"
color="primary"
- href="#/settings/filter_lists"
+ onClick={redirectToFilterLists}
isDisabled={canGetFilters === false}
>
{
data-test-subj="mlFilterListsCreateButton"
size="l"
color="primary"
- href="#/settings/filter_lists/new_filter_list"
+ onClick={redirectToNewFilterListPage}
isDisabled={canCreateFilter === false}
>
+
+
+ }
+ labelType="label"
+ >
+
+
+
+ }
+ labelType="label"
+ >
+
+
@@ -137,7 +200,6 @@ exports[`CalendarForm Renders calendar form 1`] = `
grow={false}
>
@@ -215,7 +218,7 @@ export const CalendarForm = ({
-
+
({
+ useCreateAndNavigateToMlLink: jest.fn(),
+}));
const testProps = {
calendarId: '',
canCreateCalendar: true,
@@ -31,6 +34,7 @@ const testProps = {
selectedGroupOptions: [],
selectedJobOptions: [],
showNewEventModal: jest.fn(),
+ isGlobalCalendar: false,
};
describe('CalendarForm', () => {
diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js
index 1fe16e4588bd..a5eb212ba127 100644
--- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js
+++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js
@@ -20,6 +20,7 @@ import { ImportModal } from './import_modal';
import { ml } from '../../../services/ml_api_service';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { GLOBAL_CALENDAR } from '../../../../../common/constants/calendars';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
class NewCalendarUI extends Component {
static propTypes = {
@@ -55,6 +56,16 @@ class NewCalendarUI extends Component {
this.formSetup();
}
+ returnToCalendarsManagementPage = async () => {
+ const {
+ services: {
+ http: { basePath },
+ application: { navigateToUrl },
+ },
+ } = this.props.kibana;
+ await navigateToUrl(`${basePath.get()}/app/ml/${ML_PAGES.CALENDARS_MANAGE}`, true);
+ };
+
async formSetup() {
try {
const { jobIds, groupIds, calendars } = await getCalendarSettingsData();
@@ -146,7 +157,7 @@ class NewCalendarUI extends Component {
try {
await ml.addCalendar(calendar);
- window.location = '#/settings/calendars_list';
+ await this.returnToCalendarsManagementPage();
} catch (error) {
console.log('Error saving calendar', error);
this.setState({ saving: false });
@@ -167,7 +178,7 @@ class NewCalendarUI extends Component {
try {
await ml.updateCalendar(calendar);
- window.location = '#/settings/calendars_list';
+ await this.returnToCalendarsManagementPage();
} catch (error) {
console.log('Error saving calendar', error);
this.setState({ saving: false });
diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js
index 2cff255bd1ce..068d44330008 100644
--- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js
+++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js
@@ -4,6 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+jest.mock('../../../contexts/kibana/use_create_url', () => ({
+ useCreateAndNavigateToMlLink: jest.fn(),
+}));
+
jest.mock('../../../components/navigation_menu', () => ({
NavigationMenu: () => ,
}));
diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap b/x-pack/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap
index cc1c524c19b5..50cacd7b3545 100644
--- a/x-pack/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap
+++ b/x-pack/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap
@@ -77,7 +77,6 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = `
"toolsRight": Array [
diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js
index 77331c4a987d..6b4403aef7c7 100644
--- a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js
+++ b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.js
@@ -7,12 +7,14 @@
import PropTypes from 'prop-types';
import React from 'react';
-import { EuiButton, EuiLink, EuiInMemoryTable } from '@elastic/eui';
-
+import { EuiButton, EuiInMemoryTable } from '@elastic/eui';
+import { Link } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { GLOBAL_CALENDAR } from '../../../../../../common/constants/calendars';
+import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
+import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
export const CalendarsListTable = ({
calendarsList,
@@ -24,6 +26,8 @@ export const CalendarsListTable = ({
mlNodesAvailable,
itemsSelected,
}) => {
+ const redirectToNewCalendarPage = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_NEW);
+
const sorting = {
sort: {
field: 'calendar_id',
@@ -46,12 +50,9 @@ export const CalendarsListTable = ({
truncateText: true,
scope: 'row',
render: (id) => (
-
+
{id}
-
+
),
'data-test-subj': 'mlCalendarListColumnId',
},
@@ -101,7 +102,7 @@ export const CalendarsListTable = ({
size="s"
data-test-subj="mlCalendarButtonCreate"
key="new_calendar_button"
- href="#/settings/calendars_list/new_calendar"
+ onClick={redirectToNewCalendarPage}
isDisabled={canCreateCalendar === false || mlNodesAvailable === false}
>
@@ -115,6 +116,7 @@ export const CalendarsListTable = ({
canDeleteCalendar === false || mlNodesAvailable === false || itemsSelected === false
}
data-test-subj="mlCalendarButtonDelete"
+ key="delete_calendar_button"
>
({
+ useCreateAndNavigateToMlLink: jest.fn(),
+}));
const calendars = [
{
@@ -42,7 +47,11 @@ describe('CalendarsListTable', () => {
});
test('New button enabled if permission available', () => {
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+
+
+ );
const buttons = wrapper.find('[data-test-subj="mlCalendarButtonCreate"]');
const button = buttons.find('EuiButton');
@@ -56,7 +65,11 @@ describe('CalendarsListTable', () => {
canCreateCalendar: false,
};
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+
+
+ );
const buttons = wrapper.find('[data-test-subj="mlCalendarButtonCreate"]');
const button = buttons.find('EuiButton');
@@ -70,7 +83,11 @@ describe('CalendarsListTable', () => {
mlNodesAvailable: false,
};
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+
+
+ );
const buttons = wrapper.find('[data-test-subj="mlCalendarButtonCreate"]');
const button = buttons.find('EuiButton');
diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js
index 41b7aa63f55e..681c54ca9eee 100644
--- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js
+++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js
@@ -34,6 +34,7 @@ import { ItemsGrid } from '../../../components/items_grid';
import { NavigationMenu } from '../../../components/navigation_menu';
import { isValidFilterListId, saveFilterList } from './utils';
import { ml } from '../../../services/ml_api_service';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
const DEFAULT_ITEMS_PER_PAGE = 50;
@@ -67,10 +68,6 @@ function getActivePage(activePageState, itemsPerPage, numMatchingItems) {
return activePage;
}
-function returnToFiltersList() {
- window.location.href = `#/settings/filter_lists`;
-}
-
export class EditFilterListUI extends Component {
static displayName = 'EditFilterList';
static propTypes = {
@@ -105,6 +102,16 @@ export class EditFilterListUI extends Component {
}
}
+ returnToFiltersList = async () => {
+ const {
+ services: {
+ http: { basePath },
+ application: { navigateToUrl },
+ },
+ } = this.props.kibana;
+ await navigateToUrl(`${basePath.get()}/app/ml/${ML_PAGES.FILTER_LISTS_MANAGE}`, true);
+ };
+
loadFilterList = (filterId) => {
ml.filters
.filters({ filterId })
@@ -279,7 +286,7 @@ export class EditFilterListUI extends Component {
saveFilterList(filterId, description, items, loadedFilter)
.then((savedFilter) => {
this.setLoadedFilterState(savedFilter);
- returnToFiltersList();
+ this.returnToFiltersList();
})
.catch((resp) => {
console.log(`Error saving filter ${filterId}:`, resp);
@@ -355,7 +362,7 @@ export class EditFilterListUI extends Component {
/>
-
+ this.returnToFiltersList()}>
@@ -84,12 +88,9 @@ function getColumns() {
defaultMessage: 'ID',
}),
render: (id) => (
-
+
{id}
-
+
),
sortable: true,
scope: 'row',
@@ -213,7 +214,7 @@ export function FilterListsTable({
isSelectable={true}
data-test-subj="mlFilterListsTable"
rowProps={(item) => ({
- 'data-test-subj': `mlCalendarListRow row-${item.filter_id}`,
+ 'data-test-subj': `mlFilterListsRow row-${item.filter_id}`,
})}
/>
diff --git a/x-pack/plugins/ml/public/application/settings/settings.test.tsx b/x-pack/plugins/ml/public/application/settings/settings.test.tsx
index f16bf6263215..a5e69f233e2d 100644
--- a/x-pack/plugins/ml/public/application/settings/settings.test.tsx
+++ b/x-pack/plugins/ml/public/application/settings/settings.test.tsx
@@ -22,6 +22,10 @@ jest.mock('../contexts/kibana', () => ({
},
}));
+jest.mock('../contexts/kibana/use_create_url', () => ({
+ useCreateAndNavigateToMlLink: jest.fn(),
+}));
+
describe('Settings', () => {
function runCheckButtonsDisabledTest(
canGetFilters: boolean,
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx
index deecb9fb45b5..88bf769aa293 100644
--- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx
+++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx
@@ -12,26 +12,40 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { useMlUrlGenerator, useNavigateToPath } from '../../../contexts/kibana';
+import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
-export const TimeseriesexplorerNoJobsFound = () => (
-
-
-
- }
- actions={
-
-
-
- }
- />
-);
+export const TimeseriesexplorerNoJobsFound = () => {
+ const mlUrlGenerator = useMlUrlGenerator();
+ const navigateToPath = useNavigateToPath();
+
+ const redirectToJobsManagementPage = async () => {
+ const path = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ });
+ await navigateToPath(path, true);
+ };
+
+ return (
+
+
+
+ }
+ actions={
+
+
+
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js
index 4ec7c5cb6d81..ca55bb10b13d 100644
--- a/x-pack/plugins/ml/public/application/util/chart_utils.js
+++ b/x-pack/plugins/ml/public/application/util/chart_utils.js
@@ -8,11 +8,9 @@ import d3 from 'd3';
import { calculateTextWidth } from './string_utils';
import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact';
import moment from 'moment';
-import rison from 'rison-node';
-
import { getTimefilter } from './dependency_cache';
-
import { CHART_TYPE } from '../explorer/explorer_constants';
+import { ML_PAGES } from '../../../common/constants/ml_url_generator';
export const LINE_CHART_ANOMALY_RADIUS = 7;
export const MULTI_BUCKET_SYMBOL_SIZE = 100; // In square pixels for use with d3 symbol.size
@@ -212,7 +210,7 @@ export function getChartType(config) {
return chartType;
}
-export function getExploreSeriesLink(series) {
+export async function getExploreSeriesLink(mlUrlGenerator, series) {
// Open the Single Metric dashboard over the same overall bounds and
// zoomed in to the same time as the current chart.
const timefilter = getTimefilter();
@@ -227,46 +225,44 @@ export function getExploreSeriesLink(series) {
// to identify the particular series to view.
// Initially pass them in the mlTimeSeriesExplorer part of the AppState.
// TODO - do we want to pass the entities via the filter?
- const entityCondition = {};
- series.entityFields.forEach((entity) => {
- entityCondition[entity.fieldName] = entity.fieldValue;
- });
+ let entityCondition;
+ if (series.entityFields.length > 0) {
+ entityCondition = {};
+ series.entityFields.forEach((entity) => {
+ entityCondition[entity.fieldName] = entity.fieldValue;
+ });
+ }
- // Use rison to build the URL .
- const _g = rison.encode({
- ml: {
+ const url = await mlUrlGenerator.createUrl({
+ page: ML_PAGES.SINGLE_METRIC_VIEWER,
+ pageState: {
jobIds: [series.jobId],
- },
- refreshInterval: {
- display: 'Off',
- pause: false,
- value: 0,
- },
- time: {
- from: from,
- to: to,
- mode: 'absolute',
- },
- });
-
- const _a = rison.encode({
- mlTimeSeriesExplorer: {
+ refreshInterval: {
+ display: 'Off',
+ pause: false,
+ value: 0,
+ },
+ timeRange: {
+ from: from,
+ to: to,
+ mode: 'absolute',
+ },
zoom: {
from: zoomFrom,
to: zoomTo,
},
detectorIndex: series.detectorIndex,
entities: entityCondition,
- },
- query: {
- query_string: {
- analyze_wildcard: true,
- query: '*',
+ query: {
+ query_string: {
+ analyze_wildcard: true,
+ query: '*',
+ },
},
},
+ excludeBasePath: true,
});
-
- return `#/timeseriesexplorer?_g=${_g}&_a=${encodeURIComponent(_a)}`;
+ return url;
}
export function showMultiBucketAnomalyMarker(point) {
diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.test.js b/x-pack/plugins/ml/public/application/util/chart_utils.test.js
index b7cf11c088a1..955dd7cbea0a 100644
--- a/x-pack/plugins/ml/public/application/util/chart_utils.test.js
+++ b/x-pack/plugins/ml/public/application/util/chart_utils.test.js
@@ -35,7 +35,6 @@ import { render } from '@testing-library/react';
import {
chartLimits,
getChartType,
- getExploreSeriesLink,
getTickValues,
getXTransform,
isLabelLengthAboveThreshold,
@@ -238,20 +237,6 @@ describe('ML - chart utils', () => {
});
});
- describe('getExploreSeriesLink', () => {
- test('get timeseriesexplorer link', () => {
- const link = getExploreSeriesLink(seriesConfig);
- const expectedLink =
- `#/timeseriesexplorer?_g=(ml:(jobIds:!(population-03)),` +
- `refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2017-02-23T00:00:00.000Z',mode:absolute,` +
- `to:'2017-02-23T23:59:59.999Z'))&_a=(mlTimeSeriesExplorer%3A(detectorIndex%3A0%2Centities%3A` +
- `(nginx.access.remote_ip%3A'72.57.0.53')%2Czoom%3A(from%3A'2017-02-19T20%3A00%3A00.000Z'%2Cto%3A'2017-02-27T04%3A00%3A00.000Z'))` +
- `%2Cquery%3A(query_string%3A(analyze_wildcard%3A!t%2Cquery%3A'*')))`;
-
- expect(link).toBe(expectedLink);
- });
- });
-
describe('numTicks', () => {
test('returns 10 for 1000', () => {
expect(numTicks(1000)).toBe(10);
diff --git a/x-pack/plugins/ml/public/application/util/get_selected_ids_url.ts b/x-pack/plugins/ml/public/application/util/get_selected_ids_url.ts
deleted file mode 100644
index 806626577008..000000000000
--- a/x-pack/plugins/ml/public/application/util/get_selected_ids_url.ts
+++ /dev/null
@@ -1,39 +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 rison from 'rison-node';
-import { getBasePath } from './dependency_cache';
-
-export enum TAB_IDS {
- DATA_FRAME_ANALYTICS = 'data_frame_analytics',
- ANOMALY_DETECTION = 'jobs',
-}
-
-function getSelectedIdsUrl(tabId: TAB_IDS, settings: { [key: string]: string | string[] }): string {
- // Create url for filtering by job id or group ids for kibana management table
- const encoded = rison.encode(settings);
- const url = `?mlManagement=${encoded}`;
- const basePath = getBasePath();
-
- return `${basePath.get()}/app/ml#/${tabId}${url}`;
-}
-
-// Create url for filtering by group ids for kibana management table
-export function getGroupIdsUrl(tabId: TAB_IDS, ids: string[]): string {
- const settings = {
- groupIds: ids,
- };
-
- return getSelectedIdsUrl(tabId, settings);
-}
-
-// Create url for filtering by job id for kibana management table
-export function getJobIdUrl(tabId: TAB_IDS, id: string): string {
- const settings = {
- jobId: id,
- };
-
- return getSelectedIdsUrl(tabId, settings);
-}
diff --git a/x-pack/plugins/ml/public/application/util/recently_accessed.ts b/x-pack/plugins/ml/public/application/util/recently_accessed.ts
index ab879e421cb0..04ccd84c561b 100644
--- a/x-pack/plugins/ml/public/application/util/recently_accessed.ts
+++ b/x-pack/plugins/ml/public/application/util/recently_accessed.ts
@@ -37,7 +37,7 @@ export function addItemToRecentlyAccessed(page: string, itemId: string, url: str
return;
}
- url = `ml#/${page}/${url}`;
+ url = url.startsWith('/') ? `/app/ml${url}` : `/app/ml/${page}/${url}`;
const recentlyAccessed = getRecentlyAccessed();
recentlyAccessed.add(url, `ML - ${itemId} - ${pageLabel}`, id);
}
diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts
index c4aebb108e7b..6a44756412fe 100644
--- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts
+++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts
@@ -11,13 +11,14 @@ import {
ExplorerAppState,
ExplorerGlobalState,
ExplorerUrlState,
+ MlCommonGlobalState,
MlGenericUrlState,
TimeSeriesExplorerAppState,
TimeSeriesExplorerGlobalState,
TimeSeriesExplorerUrlState,
} from '../../common/types/ml_url_generator';
import { ML_PAGES } from '../../common/constants/ml_url_generator';
-import { createIndexBasedMlUrl } from './common';
+import { createGenericMlUrl } from './common';
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
/**
* Creates URL to the Anomaly Detection Job management page
@@ -30,18 +31,29 @@ export function createAnomalyDetectionJobManagementUrl(
if (!params || isEmpty(params)) {
return url;
}
- const { jobId, groupIds } = params;
- const queryState: AnomalyDetectionQueryState = {
- jobId,
- groupIds,
- };
+ const { jobId, groupIds, globalState } = params;
+ if (jobId || groupIds) {
+ const queryState: AnomalyDetectionQueryState = {
+ jobId,
+ groupIds,
+ };
- url = setStateToKbnUrl(
- 'mlManagement',
- queryState,
- { useHash: false, storeInHashQuery: false },
- url
- );
+ url = setStateToKbnUrl(
+ 'mlManagement',
+ queryState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
+ }
+
+ if (globalState) {
+ url = setStateToKbnUrl>(
+ '_g',
+ globalState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
+ }
return url;
}
@@ -49,13 +61,24 @@ export function createAnomalyDetectionCreateJobSelectType(
appBasePath: string,
pageState: MlGenericUrlState['pageState']
): string {
- return createIndexBasedMlUrl(
+ return createGenericMlUrl(
appBasePath,
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
pageState
);
}
+export function createAnomalyDetectionCreateJobSelectIndex(
+ appBasePath: string,
+ pageState: MlGenericUrlState['pageState']
+): string {
+ return createGenericMlUrl(
+ appBasePath,
+ ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX,
+ pageState
+ );
+}
+
/**
* Creates URL to the Anomaly Explorer page
*/
@@ -75,36 +98,35 @@ export function createExplorerUrl(
query,
mlExplorerSwimlane = {},
mlExplorerFilter = {},
+ globalState,
} = params;
const appState: Partial = {
mlExplorerSwimlane,
mlExplorerFilter,
};
+ let queryState: Partial = {};
+ if (globalState) queryState = globalState;
if (query) appState.query = query;
-
if (jobIds) {
- const queryState: Partial = {
- ml: {
- jobIds,
- },
+ queryState.ml = {
+ jobIds,
};
-
- if (timeRange) queryState.time = timeRange;
- if (refreshInterval) queryState.refreshInterval = refreshInterval;
-
- url = setStateToKbnUrl>(
- '_g',
- queryState,
- { useHash: false, storeInHashQuery: false },
- url
- );
- url = setStateToKbnUrl>(
- '_a',
- appState,
- { useHash: false, storeInHashQuery: false },
- url
- );
}
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+ if (timeRange) queryState.time = timeRange;
+
+ url = setStateToKbnUrl>(
+ '_g',
+ queryState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
+ url = setStateToKbnUrl>(
+ '_a',
+ appState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
return url;
}
@@ -120,19 +142,36 @@ export function createSingleMetricViewerUrl(
if (!params) {
return url;
}
- const { timeRange, jobIds, refreshInterval, zoom, query, detectorIndex, entities } = params;
-
- const queryState: TimeSeriesExplorerGlobalState = {
- ml: {
- jobIds,
- },
+ const {
+ timeRange,
+ jobIds,
refreshInterval,
- time: timeRange,
- };
+ zoom,
+ query,
+ detectorIndex,
+ forecastId,
+ entities,
+ globalState,
+ } = params;
+
+ let queryState: Partial = {};
+ if (globalState) queryState = globalState;
+
+ if (jobIds) {
+ queryState.ml = {
+ jobIds,
+ };
+ }
+ if (refreshInterval) queryState.refreshInterval = refreshInterval;
+ if (timeRange) queryState.time = timeRange;
const appState: Partial = {};
const mlTimeSeriesExplorer: Partial = {};
+ if (forecastId !== undefined) {
+ mlTimeSeriesExplorer.forecastId = forecastId;
+ }
+
if (detectorIndex !== undefined) {
mlTimeSeriesExplorer.detectorIndex = detectorIndex;
}
@@ -146,7 +185,7 @@ export function createSingleMetricViewerUrl(
appState.query = {
query_string: query,
};
- url = setStateToKbnUrl(
+ url = setStateToKbnUrl>(
'_g',
queryState,
{ useHash: false, storeInHashQuery: false },
diff --git a/x-pack/plugins/ml/public/ml_url_generator/common.ts b/x-pack/plugins/ml/public/ml_url_generator/common.ts
index 57cfc5204528..f929e513e618 100644
--- a/x-pack/plugins/ml/public/ml_url_generator/common.ts
+++ b/x-pack/plugins/ml/public/ml_url_generator/common.ts
@@ -19,37 +19,40 @@ export function extractParams(urlState: UrlState) {
* Creates generic index based search ML url
* e.g. `jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a`
*/
-export function createIndexBasedMlUrl(
+export function createGenericMlUrl(
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 (pageState) {
+ const { globalState, appState, index, savedSearchId, ...restParams } = pageState;
+ 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);
+ }
}
- 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;
}
diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts
index 8cf10a2acb64..88761edf241a 100644
--- a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts
+++ b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts
@@ -12,6 +12,7 @@ import {
DataFrameAnalyticsExplorationUrlState,
DataFrameAnalyticsQueryState,
DataFrameAnalyticsUrlState,
+ MlCommonGlobalState,
} from '../../common/types/ml_url_generator';
import { ML_PAGES } from '../../common/constants/ml_url_generator';
import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public';
@@ -23,18 +24,28 @@ export function createDataFrameAnalyticsJobManagementUrl(
let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`;
if (mlUrlGeneratorState) {
- const { jobId, groupIds } = mlUrlGeneratorState;
- const queryState: Partial = {
- jobId,
- groupIds,
- };
+ const { jobId, groupIds, globalState } = mlUrlGeneratorState;
+ if (jobId || groupIds) {
+ const queryState: Partial = {
+ jobId,
+ groupIds,
+ };
- url = setStateToKbnUrl>(
- 'mlManagement',
- queryState,
- { useHash: false, storeInHashQuery: false },
- url
- );
+ url = setStateToKbnUrl>(
+ 'mlManagement',
+ queryState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
+ }
+ if (globalState) {
+ url = setStateToKbnUrl>(
+ '_g',
+ globalState,
+ { useHash: false, storeInHashQuery: false },
+ url
+ );
+ }
}
return url;
@@ -50,12 +61,14 @@ export function createDataFrameAnalyticsExplorationUrl(
let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`;
if (mlUrlGeneratorState) {
- const { jobId, analysisType } = mlUrlGeneratorState;
+ const { jobId, analysisType, globalState } = mlUrlGeneratorState;
+
const queryState: DataFrameAnalyticsExplorationQueryState = {
ml: {
jobId,
analysisType,
},
+ ...globalState,
};
url = setStateToKbnUrl(
diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts
deleted file mode 100644
index 24693df5025d..000000000000
--- a/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts
+++ /dev/null
@@ -1,29 +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.
- */
-
-/**
- * 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);
-}
diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts
index 55bc6d3668de..754f5bec57a0 100644
--- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts
+++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts
@@ -6,7 +6,7 @@
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';
+import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics';
describe('MlUrlGenerator', () => {
const urlGenerator = new MlUrlGenerator({
diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts
index b69260d8d415..abec5cc2b7d1 100644
--- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts
+++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts
@@ -16,6 +16,7 @@ import { MlUrlGeneratorState } from '../../common/types/ml_url_generator';
import {
createAnomalyDetectionJobManagementUrl,
createAnomalyDetectionCreateJobSelectType,
+ createAnomalyDetectionCreateJobSelectIndex,
createExplorerUrl,
createSingleMetricViewerUrl,
} from './anomaly_detection_urls_generator';
@@ -23,10 +24,8 @@ import {
createDataFrameAnalyticsJobManagementUrl,
createDataFrameAnalyticsExplorationUrl,
} from './data_frame_analytics_urls_generator';
-import {
- createIndexDataVisualizerUrl,
- createDataVisualizerUrl,
-} from './data_visualizer_urls_generator';
+import { createGenericMlUrl } from './common';
+import { createEditCalendarUrl, createEditFilterUrl } from './settings_urls_generator';
declare module '../../../../../src/plugins/share/public' {
export interface UrlGeneratorStateMapping {
@@ -44,8 +43,12 @@ export class MlUrlGenerator implements UrlGeneratorsDefinition => {
- const appBasePath = this.params.appBasePath;
+ public readonly createUrl = async (
+ mlUrlGeneratorParams: MlUrlGeneratorState
+ ): Promise => {
+ const { excludeBasePath, ...mlUrlGeneratorState } = mlUrlGeneratorParams;
+ const appBasePath = excludeBasePath === true ? '' : this.params.appBasePath;
+
switch (mlUrlGeneratorState.page) {
case ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE:
return createAnomalyDetectionJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState);
@@ -56,18 +59,39 @@ export class MlUrlGenerator implements UrlGeneratorsDefinition {
defaultMessage: 'Import your own CSV, NDJSON, or log file.',
}),
icon: 'document',
- path: '/app/ml#/filedatavisualizer',
+ path: '/app/ml/filedatavisualizer',
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
order: 520,
diff --git a/x-pack/test/functional/services/ml/settings_filter_list.ts b/x-pack/test/functional/services/ml/settings_filter_list.ts
index fb1f203b6556..0afe9f21b03a 100644
--- a/x-pack/test/functional/services/ml/settings_filter_list.ts
+++ b/x-pack/test/functional/services/ml/settings_filter_list.ts
@@ -17,7 +17,7 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro
const $ = await table.parseDomContent();
const rows = [];
- for (const tr of $.findTestSubjects('~mlCalendarListRow').toArray()) {
+ for (const tr of $.findTestSubjects('~mlFilterListsRow').toArray()) {
const $tr = $(tr);
const inUseSubject = $tr