mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Migrate internal urls to non-hash paths (#76735)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
11f100b1ac
commit
d88b3a6dde
121 changed files with 2294 additions and 1038 deletions
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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<PageType, PageState> = 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<typeof ML_PAGES.ANOMALY_EXPLORER, ExplorerUrlPageState>;
|
||||
|
@ -122,6 +129,7 @@ export interface TimeSeriesExplorerAppState {
|
|||
to?: string;
|
||||
};
|
||||
mlTimeSeriesExplorer?: {
|
||||
forecastId?: string;
|
||||
detectorIndex?: number;
|
||||
entities?: Record<string, string>;
|
||||
};
|
||||
|
@ -131,10 +139,12 @@ export interface TimeSeriesExplorerAppState {
|
|||
export interface TimeSeriesExplorerPageState
|
||||
extends Pick<TimeSeriesExplorerAppState, 'zoom' | 'query'>,
|
||||
Pick<TimeSeriesExplorerGlobalState, 'refreshInterval'> {
|
||||
jobIds: JobId[];
|
||||
jobIds?: JobId[];
|
||||
timeRange?: TimeRange;
|
||||
detectorIndex?: number;
|
||||
entities?: Record<string, string>;
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"embeddable",
|
||||
"uiActions",
|
||||
"kibanaLegacy",
|
||||
"indexPatternManagement"
|
||||
"indexPatternManagement",
|
||||
"discover"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home",
|
||||
|
|
|
@ -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<MlSetupDependencies, 'share' | 'indexPatternManagement'> &
|
||||
MlStartDependencies;
|
||||
|
@ -50,11 +51,21 @@ export interface MlServicesContext {
|
|||
export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>;
|
||||
|
||||
const App: FC<AppProps> = ({ 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',
|
||||
|
|
|
@ -33,10 +33,12 @@ export function checkGetManagementMlJobsResolver() {
|
|||
});
|
||||
}
|
||||
|
||||
export function checkGetJobsCapabilitiesResolver(): Promise<MlCapabilities> {
|
||||
export function checkGetJobsCapabilitiesResolver(
|
||||
redirectToMlAccessDeniedPage: () => Promise<void>
|
||||
): Promise<MlCapabilities> {
|
||||
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<MlCapabilities> {
|
|||
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<MlCapabilities> {
|
||||
export function checkCreateJobsCapabilitiesResolver(
|
||||
redirectToJobsManagementPage: () => Promise<void>
|
||||
): Promise<MlCapabilities> {
|
||||
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<MlCapabilities> {
|
|||
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<MlCapabilities> {
|
||||
export function checkFindFileStructurePrivilegeResolver(
|
||||
redirectToMlAccessDeniedPage: () => Promise<void>
|
||||
): Promise<MlCapabilities> {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,170 +1,527 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AnnotationsTable Initialization with annotations prop. 1`] = `
|
||||
<Fragment>
|
||||
<EuiInMemoryTable
|
||||
className="eui-textOverflowWrap"
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "annotation",
|
||||
"name": "Annotation",
|
||||
"scope": "row",
|
||||
"sortable": true,
|
||||
"width": "40%",
|
||||
},
|
||||
Object {
|
||||
"dataType": "date",
|
||||
"field": "timestamp",
|
||||
"name": "From",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"dataType": "date",
|
||||
"field": "end_timestamp",
|
||||
"name": "To",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"dataType": "date",
|
||||
"field": "modified_time",
|
||||
"name": "Last modified date",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "modified_username",
|
||||
"name": "Last modified by",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "event",
|
||||
"name": "Event",
|
||||
"sortable": true,
|
||||
"width": "10%",
|
||||
},
|
||||
Object {
|
||||
"field": "partition_field_value",
|
||||
"name": "Partition",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "over_field_value",
|
||||
"name": "Over",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "by_field_value",
|
||||
"name": "By",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"render": [Function],
|
||||
},
|
||||
],
|
||||
"align": "right",
|
||||
"name": "Actions",
|
||||
"width": "60px",
|
||||
},
|
||||
Object {
|
||||
"dataType": "boolean",
|
||||
"field": "current_series",
|
||||
"name": "current_series",
|
||||
"render": [Function],
|
||||
"width": "0px",
|
||||
},
|
||||
]
|
||||
}
|
||||
compressed={true}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"_id": "KCCkDWgB_ZdQ1MFDSYPi",
|
||||
"annotation": "Major spike.",
|
||||
"create_time": 1546417097181,
|
||||
"create_username": "<user unknown>",
|
||||
"end_timestamp": 1455041968976,
|
||||
"job_id": "farequote",
|
||||
"modified_time": 1546417097181,
|
||||
"modified_username": "<user unknown>",
|
||||
"timestamp": 1455026177994,
|
||||
"type": "annotation",
|
||||
},
|
||||
]
|
||||
}
|
||||
pagination={
|
||||
<AnnotationsTableUI
|
||||
annotations={
|
||||
Array [
|
||||
Object {
|
||||
"pageSizeOptions": Array [
|
||||
5,
|
||||
10,
|
||||
25,
|
||||
],
|
||||
}
|
||||
}
|
||||
responsive={true}
|
||||
rowProps={[Function]}
|
||||
search={
|
||||
Object {
|
||||
"box": Object {
|
||||
"incremental": true,
|
||||
"schema": true,
|
||||
},
|
||||
"defaultQuery": "event:(user or delayed_data)",
|
||||
"filters": Array [
|
||||
Object {
|
||||
"field": "event",
|
||||
"multiSelect": "or",
|
||||
"name": "Event",
|
||||
"options": Array [],
|
||||
"type": "field_value_selection",
|
||||
"_id": "KCCkDWgB_ZdQ1MFDSYPi",
|
||||
"annotation": "Major spike.",
|
||||
"create_time": 1546417097181,
|
||||
"create_username": "<user unknown>",
|
||||
"end_timestamp": 1455041968976,
|
||||
"job_id": "farequote",
|
||||
"modified_time": 1546417097181,
|
||||
"modified_username": "<user unknown>",
|
||||
"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`] = `
|
||||
<AnnotationsTableUI
|
||||
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",
|
||||
},
|
||||
},
|
||||
"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,
|
||||
}
|
||||
}
|
||||
jobs={
|
||||
Array [
|
||||
Object {
|
||||
"analysis_config": Object {
|
||||
"bucket_span": "15m",
|
||||
"detectors": Array [
|
||||
Object {
|
||||
"detector_description": "count",
|
||||
"detector_index": 0,
|
||||
"function": "count",
|
||||
},
|
||||
],
|
||||
"influencers": Array [],
|
||||
"summary_count_field_name": "doc_count",
|
||||
},
|
||||
"analysis_limits": Object {
|
||||
"categorization_examples_limit": 4,
|
||||
"model_memory_limit": "10mb",
|
||||
},
|
||||
"create_time": 1546418356716,
|
||||
"custom_settings": Object {
|
||||
"created_by": "single-metric-wizard",
|
||||
},
|
||||
"data_counts": Object {
|
||||
"bucket_count": 478,
|
||||
"earliest_record_timestamp": 1454804096000,
|
||||
"empty_bucket_count": 0,
|
||||
"input_bytes": 21554,
|
||||
"input_field_count": 479,
|
||||
"input_record_count": 479,
|
||||
"invalid_date_count": 0,
|
||||
"job_id": "farequote",
|
||||
"last_data_time": 1546418357578,
|
||||
"latest_record_timestamp": 1455234298000,
|
||||
"missing_field_count": 0,
|
||||
"out_of_order_timestamp_count": 0,
|
||||
"processed_field_count": 479,
|
||||
"processed_record_count": 479,
|
||||
"sparse_bucket_count": 0,
|
||||
},
|
||||
"data_description": Object {
|
||||
"time_field": "@timestamp",
|
||||
"time_format": "epoch_ms",
|
||||
},
|
||||
"datafeed_config": Object {
|
||||
"aggregations": Object {
|
||||
"buckets": Object {
|
||||
"aggregations": Object {
|
||||
"@timestamp": Object {
|
||||
"max": Object {
|
||||
"field": "@timestamp",
|
||||
},
|
||||
},
|
||||
},
|
||||
"date_histogram": Object {
|
||||
"field": "@timestamp",
|
||||
"fixed_interval": "15m",
|
||||
"keyed": false,
|
||||
"min_doc_count": 0,
|
||||
"offset": 0,
|
||||
"order": Object {
|
||||
"_key": "asc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"chunking_config": Object {
|
||||
"mode": "manual",
|
||||
"time_span": "900000000ms",
|
||||
},
|
||||
"datafeed_id": "datafeed-farequote",
|
||||
"delayed_data_check_config": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
"indices": Array [
|
||||
"farequote",
|
||||
],
|
||||
"job_id": "farequote",
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"adjust_pure_negative": true,
|
||||
"boost": 1,
|
||||
"must": Array [
|
||||
Object {
|
||||
"query_string": Object {
|
||||
"analyze_wildcard": true,
|
||||
"auto_generate_synonyms_phrase_query": true,
|
||||
"boost": 1,
|
||||
"default_operator": "or",
|
||||
"enable_position_increments": true,
|
||||
"escape": false,
|
||||
"fields": Array [],
|
||||
"fuzziness": "AUTO",
|
||||
"fuzzy_max_expansions": 50,
|
||||
"fuzzy_prefix_length": 0,
|
||||
"fuzzy_transpositions": true,
|
||||
"max_determinized_states": 10000,
|
||||
"phrase_slop": 0,
|
||||
"query": "*",
|
||||
"type": "best_fields",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"query_delay": "115823ms",
|
||||
"scroll_size": 1000,
|
||||
"state": "stopped",
|
||||
},
|
||||
"description": "",
|
||||
"established_model_memory": 42102,
|
||||
"finished_time": 1546418359427,
|
||||
"job_id": "farequote",
|
||||
"job_type": "anomaly_detector",
|
||||
"job_version": "7.0.0",
|
||||
"model_plot_config": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
"model_size_stats": Object {
|
||||
"bucket_allocation_failures_count": 0,
|
||||
"job_id": "farequote",
|
||||
"log_time": 1546418359000,
|
||||
"memory_status": "ok",
|
||||
"model_bytes": 42102,
|
||||
"result_type": "model_size_stats",
|
||||
"timestamp": 1455232500000,
|
||||
"total_by_field_count": 3,
|
||||
"total_over_field_count": 0,
|
||||
"total_partition_field_count": 2,
|
||||
},
|
||||
"model_snapshot_id": "1546418359",
|
||||
"model_snapshot_min_version": "6.4.0",
|
||||
"model_snapshot_retention_days": 1,
|
||||
"results_index_name": "shared",
|
||||
"state": "closed",
|
||||
},
|
||||
]
|
||||
}
|
||||
kibana={
|
||||
Object {
|
||||
"notifications": Object {
|
||||
"toasts": Object {
|
||||
"danger": [Function],
|
||||
"show": [Function],
|
||||
"success": [Function],
|
||||
"warning": [Function],
|
||||
},
|
||||
},
|
||||
"overlays": Object {
|
||||
"openFlyout": [Function],
|
||||
"openModal": [Function],
|
||||
},
|
||||
"services": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`AnnotationsTable Minimal initialization without props. 1`] = `
|
||||
<AnnotationsTableUI
|
||||
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",
|
||||
},
|
||||
},
|
||||
"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 {},
|
||||
}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`AnnotationsTable Initialization with job config prop. 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="l"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`AnnotationsTable Minimal initialization without props. 1`] = `
|
||||
<EuiCallOut
|
||||
iconType="iInCircle"
|
||||
role="alert"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="No annotations created for this job"
|
||||
id="xpack.ml.annotationsTable.annotationsNotCreatedTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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(
|
||||
<I18nProvider>
|
||||
<Router history={history}>
|
||||
<AnomalyResultsViewSelector viewId="timeseriesexplorer" />
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('mlAnomalyResultsViewSelectorExplorer'));
|
||||
expect(mockedOpen).toHaveBeenCalledWith('#/explorer', '_self');
|
||||
|
||||
// Clean-up window.open.
|
||||
window.open = originalOpen;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Props> = ({ viewId }) => {
|
||||
const urlGenerator = useMlUrlGenerator();
|
||||
const navigateToPath = useNavigateToPath();
|
||||
|
||||
const toggleButtonsIcons = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -28,7 +32,7 @@ export const AnomalyResultsViewSelector: FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ viewId }) => {
|
|||
data-test-subj="mlAnomalyResultsViewSelector"
|
||||
options={toggleButtonsIcons}
|
||||
idSelected={viewId}
|
||||
onChange={onChangeView}
|
||||
onChange={(newViewId: string) => onChangeView(newViewId as Props['viewId'])}
|
||||
isIconOnly
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<TabId, TabData> = {
|
||||
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<Props> = ({ 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<Props> = ({ 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 (
|
||||
<EuiTabs display="condensed">
|
||||
{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 ? (
|
||||
<EuiTab key={`${id}-key`} className={'mlNavigationMenu__mainTab'} disabled={true}>
|
||||
|
@ -104,21 +165,18 @@ export const MainTabs: FC<Props> = ({ tabId, disableLinks }) => {
|
|||
</EuiTab>
|
||||
) : (
|
||||
<div className="euiTab" key={`div-${id}-key`}>
|
||||
<EuiLink
|
||||
<EuiTab
|
||||
data-test-subj={testSubject + (id === selectedTabId ? ' selected' : '')}
|
||||
href={`#/${defaultPathId}${fullGlobalStateString}`}
|
||||
key={`${id}-key`}
|
||||
color="text"
|
||||
className={'mlNavigationMenu__mainTab'}
|
||||
onClick={() => {
|
||||
onSelectedTabChanged(id);
|
||||
redirectToTab(defaultPathId);
|
||||
}}
|
||||
isSelected={id === selectedTabId}
|
||||
key={`tab-${id}-key`}
|
||||
>
|
||||
<EuiTab
|
||||
className={'mlNavigationMenu__mainTab'}
|
||||
onClick={() => onSelectedTabChanged(id)}
|
||||
isSelected={id === selectedTabId}
|
||||
key={`tab-${id}-key`}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
</EuiLink>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -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 (
|
||||
<EuiCallOut
|
||||
title={
|
||||
|
@ -36,7 +47,7 @@ function NoFilterListsCallOut() {
|
|||
to create the list of values you want to include or exclude in the rule."
|
||||
values={{
|
||||
filterListsLink: (
|
||||
<EuiLink href="#/settings/filter_lists">
|
||||
<EuiLink onClick={redirectToFilterManagementPage}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.ruleEditor.scopeSection.createFilterListsDescription.filterListsLinkText"
|
||||
defaultMessage="Filter Lists"
|
||||
|
|
|
@ -9,4 +9,4 @@ export { useNavigateToPath, NavigateToPath } from './use_navigate_to_path';
|
|||
export { useUiSettings } from './use_ui_settings_context';
|
||||
export { useTimefilter } from './use_timefilter';
|
||||
export { useNotifications } from './use_notifications_context';
|
||||
export { useMlUrlGenerator } from './use_create_url';
|
||||
export { useMlUrlGenerator, useMlLink } from './use_create_url';
|
||||
|
|
|
@ -22,6 +22,6 @@ interface StartPlugins {
|
|||
share: SharePluginStart;
|
||||
}
|
||||
export type StartServices = CoreStart &
|
||||
StartPlugins & { kibanaVersion: string } & MlServicesContext;
|
||||
StartPlugins & { appName: string; kibanaVersion: string } & MlServicesContext;
|
||||
export const useMlKibana = () => useKibana<StartServices>();
|
||||
export type MlKibanaReactContextValue = KibanaReactContextValue<StartServices>;
|
||||
|
|
|
@ -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<string>(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<void>) => {
|
||||
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]);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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<ANALYSIS_CONFIG_TYPE, (f: Field) => boolean> = {
|
||||
const callbacks: Record<DataFrameAnalysisConfigType, (f: Field) => boolean> = {
|
||||
[ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: containsClassificationFieldsCb,
|
||||
[ANALYSIS_CONFIG_TYPE.REGRESSION]: containsRegressionFieldsCb,
|
||||
[ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: containsOutlierFieldsCb,
|
||||
};
|
||||
|
||||
const messages: Record<ANALYSIS_CONFIG_TYPE, JSX.Element> = {
|
||||
const messages: Record<DataFrameAnalysisConfigType, JSX.Element> = {
|
||||
[ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataframe.analytics.create.sourceObjectClassificationHelpText"
|
||||
|
|
|
@ -18,13 +18,13 @@ import { ml } from '../../../../../services/ml_api_service';
|
|||
import { BackToListPanel } from '../back_to_list_panel';
|
||||
import { ViewResultsPanel } from '../view_results_panel';
|
||||
import { ProgressStats } from './progress_stats';
|
||||
import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
|
||||
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
||||
|
||||
export const PROGRESS_REFRESH_INTERVAL_MS = 1000;
|
||||
|
||||
interface Props {
|
||||
jobId: string;
|
||||
jobType: ANALYSIS_CONFIG_TYPE;
|
||||
jobType: DataFrameAnalysisConfigType;
|
||||
showProgress: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ import React, { FC, Fragment } from 'react';
|
|||
import { EuiCard, EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useMlUrlGenerator } from '../../../../../contexts/kibana';
|
||||
import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics';
|
||||
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
|
||||
import { useNavigateToPath } from '../../../../../contexts/kibana';
|
||||
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
||||
interface Props {
|
||||
jobId: string;
|
||||
analysisType: ANALYSIS_CONFIG_TYPE;
|
||||
analysisType: DataFrameAnalysisConfigType;
|
||||
}
|
||||
|
||||
export const ViewResultsPanel: FC<Props> = ({ jobId, analysisType }) => {
|
||||
|
|
|
@ -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<Props> = React.memo(
|
|||
{...classificationData}
|
||||
dataTestSubj="mlExplorationDataGrid"
|
||||
toastNotifications={getToastNotifications()}
|
||||
analysisType={(analysisType as unknown) as ANALYSIS_CONFIG_TYPE}
|
||||
analysisType={(analysisType as unknown) as DataFrameAnalysisConfigType}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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 }) => (
|
||||
<Fragment>
|
||||
<NavigationMenu tabId="data_frame_analytics" />
|
||||
|
|
|
@ -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<typeof useViewAction>;
|
||||
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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) => (
|
||||
<EuiLink href={getJobIdUrl(TAB_IDS.DATA_FRAME_ANALYTICS, item.id)}>{item.id}</EuiLink>
|
||||
);
|
||||
export const DFAnalyticsJobIdLink = ({ item }: { item: DataFrameAnalyticsListRow }) => {
|
||||
const href = useMlLink({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
|
||||
pageState: { jobId: item.id },
|
||||
});
|
||||
|
||||
return <EuiLink href={href}>{item.id}</EuiLink>;
|
||||
};
|
||||
|
||||
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 ? <DFAnalyticsJobIdLink item={item} /> : item.id,
|
||||
},
|
||||
{
|
||||
field: DataFrameAnalyticsListColumn.description,
|
||||
|
|
|
@ -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<TrainedModelStat, 'model_id'>;
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<CloneDataFrameAnalyticsConfig>,
|
||||
isClone: boolean = true
|
||||
): Partial<State['form']> {
|
||||
const jobType = Object.keys(analyticsJobConfig.analysis)[0] as ANALYSIS_CONFIG_TYPE;
|
||||
const jobType = getAnalysisType(analyticsJobConfig.analysis) as DataFrameAnalysisConfigType;
|
||||
|
||||
const resultState: Partial<State['form']> = {
|
||||
jobType,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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={
|
||||
<EuiButton target="_blank" href="management/stack/license_management/home">
|
||||
<EuiButton
|
||||
target="_blank"
|
||||
href={`${basePath.get()}/app/management/stack/license_management/home`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.datavisualizer.selector.startTrialButtonLabel"
|
||||
defaultMessage="Start trial"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useEffect } from 'react';
|
||||
import React, { FC, useState, useEffect, useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
|
||||
|
@ -12,7 +12,13 @@ import { ml } from '../../../../services/ml_api_service';
|
|||
import { isFullLicense } from '../../../../license';
|
||||
import { checkPermission } from '../../../../capabilities/check_capabilities';
|
||||
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
|
||||
import { useMlKibana } from '../../../../contexts/kibana';
|
||||
import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
|
||||
import { MlCommonGlobalState } from '../../../../../../common/types/ml_url_generator';
|
||||
import {
|
||||
DISCOVER_APP_URL_GENERATOR,
|
||||
DiscoverUrlGeneratorState,
|
||||
} from '../../../../../../../../../src/plugins/discover/public';
|
||||
|
||||
const RECHECK_DELAY_MS = 3000;
|
||||
|
||||
|
@ -36,12 +42,70 @@ export const ResultsLinks: FC<Props> = ({
|
|||
to: 'now',
|
||||
});
|
||||
const [showCreateJobLink, setShowCreateJobLink] = useState(false);
|
||||
const [globalStateString, setGlobalStateString] = useState('');
|
||||
const [globalState, setGlobalState] = useState<MlCommonGlobalState | undefined>();
|
||||
|
||||
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<void> => {
|
||||
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<Props> = ({
|
|||
}, []);
|
||||
|
||||
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<Props> = ({
|
|||
/>
|
||||
}
|
||||
description=""
|
||||
href={`${basePath.get()}/app/discover#/?&_a=(index:'${indexPatternId}')${globalStateString}`}
|
||||
href={discoverLink}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -108,7 +174,7 @@ export const ResultsLinks: FC<Props> = ({
|
|||
/>
|
||||
}
|
||||
description=""
|
||||
href={`#/jobs/new_job/step/job_type?index=${indexPatternId}${globalStateString}`}
|
||||
onClick={redirectToADCreateJobsSelectTypePage}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -124,7 +190,7 @@ export const ResultsLinks: FC<Props> = ({
|
|||
/>
|
||||
}
|
||||
description=""
|
||||
href={`#/jobs/new_job/datavisualizer?index=${indexPatternId}${globalStateString}`}
|
||||
onClick={openInDataVisualizer}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -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<Props> = ({ indexPattern }) => {
|
||||
const [recognizerResultsCount, setRecognizerResultsCount] = useState(0);
|
||||
const basePath = getBasePath();
|
||||
|
||||
const recognizerResults = {
|
||||
count: 0,
|
||||
|
@ -29,12 +28,7 @@ export const ActionsPanel: FC<Props> = ({ 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<Props> = ({ indexPattern }) => {
|
|||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<CreateJobLinkCard
|
||||
icon="createAdvancedJob"
|
||||
title={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedTitle', {
|
||||
defaultMessage: 'Advanced',
|
||||
})}
|
||||
description={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedDescription', {
|
||||
defaultMessage:
|
||||
'Use the full range of options to create a job for more advanced use cases',
|
||||
})}
|
||||
onClick={openAdvancedJobWizard}
|
||||
href={`${basePath.get()}/app/ml/jobs/new_job/advanced?index=${indexPattern.id}`}
|
||||
data-test-subj="mlDataVisualizerCreateAdvancedJobCard"
|
||||
/>
|
||||
<Link to={createJobLink}>
|
||||
<CreateJobLinkCard
|
||||
icon="createAdvancedJob"
|
||||
title={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedTitle', {
|
||||
defaultMessage: 'Advanced',
|
||||
})}
|
||||
description={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedDescription', {
|
||||
defaultMessage:
|
||||
'Use the full range of options to create a job for more advanced use cases',
|
||||
})}
|
||||
data-test-subj="mlDataVisualizerCreateAdvancedJobCard"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,17 +3,20 @@
|
|||
exports[`ExplorerNoInfluencersFound snapshot 1`] = `
|
||||
<EuiEmptyPrompt
|
||||
actions={
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={true}
|
||||
href="ml#/jobs"
|
||||
<Link
|
||||
to="/jobs"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create job"
|
||||
id="xpack.ml.explorer.createNewJobLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={true}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create job"
|
||||
id="xpack.ml.explorer.createNewJobLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</Link>
|
||||
}
|
||||
data-test-subj="mlNoJobsFound"
|
||||
iconType="alert"
|
||||
|
|
|
@ -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 = () => (
|
||||
<EuiEmptyPrompt
|
||||
iconType="alert"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage id="xpack.ml.explorer.noJobsFoundLabel" defaultMessage="No jobs found" />
|
||||
</h2>
|
||||
}
|
||||
actions={
|
||||
<EuiButton color="primary" fill href="ml#/jobs">
|
||||
<FormattedMessage id="xpack.ml.explorer.createNewJobLinkText" defaultMessage="Create job" />
|
||||
</EuiButton>
|
||||
}
|
||||
data-test-subj="mlNoJobsFound"
|
||||
/>
|
||||
);
|
||||
export const ExplorerNoJobsFound = () => {
|
||||
const ADJobsManagementUrl = useMlLink({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
|
||||
excludeBasePath: true,
|
||||
});
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="alert"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.explorer.noJobsFoundLabel"
|
||||
defaultMessage="No jobs found"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
actions={
|
||||
<Link to={ADJobsManagementUrl}>
|
||||
<EuiButton color="primary" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.explorer.createNewJobLinkText"
|
||||
defaultMessage="Create job"
|
||||
/>
|
||||
</EuiButton>
|
||||
</Link>
|
||||
}
|
||||
data-test-subj="mlNoJobsFound"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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(<ExplorerNoJobsFound />);
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
<FormattedMessage id="xpack.ml.explorer.charts.viewLabel" defaultMessage="View" />
|
||||
</EuiButtonEmpty>
|
||||
|
@ -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);
|
||||
|
||||
// <EuiFlexGrid> 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}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExplorerChartsContainer = withKibana(ExplorerChartsContainerUI);
|
||||
|
|
|
@ -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(
|
||||
<I18nProvider>
|
||||
<ExplorerChartsContainer {...getDefaultChartsData()} />
|
||||
<ExplorerChartsContainer
|
||||
{...getDefaultChartsData()}
|
||||
kibana={kibanaContextMock}
|
||||
severity={10}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
|
@ -71,10 +89,11 @@ describe('ExplorerChartsContainer', () => {
|
|||
],
|
||||
chartsPerRow: 1,
|
||||
tooManyBuckets: false,
|
||||
severity: 10,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ExplorerChartsContainer {...props} />
|
||||
<ExplorerChartsContainer {...props} kibana={kibanaContextMock} />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
|
@ -98,10 +117,11 @@ describe('ExplorerChartsContainer', () => {
|
|||
],
|
||||
chartsPerRow: 1,
|
||||
tooManyBuckets: false,
|
||||
severity: 10,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ExplorerChartsContainer {...props} />
|
||||
<ExplorerChartsContainer {...props} kibana={kibanaContextMock} />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<React.Fragment>
|
||||
{singleMetricVisible && (
|
||||
<EuiToolTip position="bottom" content={openJobsInSingleMetricViewerText}>
|
||||
<EuiButtonIcon
|
||||
href={createLinkWithUserDefaults('timeseriesexplorer', jobs)}
|
||||
iconType="visLine"
|
||||
aria-label={openJobsInSingleMetricViewerText}
|
||||
className="results-button"
|
||||
isDisabled={singleMetricEnabled === false || jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInSingleMetricViewerButton"
|
||||
/>
|
||||
{isManagementTable ? (
|
||||
<EuiButtonIcon
|
||||
href={`${basePath.get()}/app/ml/${timeSeriesExplorerLink}`}
|
||||
iconType="visLine"
|
||||
aria-label={openJobsInSingleMetricViewerText}
|
||||
className="results-button"
|
||||
isDisabled={singleMetricEnabled === false || jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInSingleMetricViewerFromManagementButton"
|
||||
/>
|
||||
) : (
|
||||
<Link to={timeSeriesExplorerLink}>
|
||||
<EuiButtonIcon
|
||||
iconType="visLine"
|
||||
aria-label={openJobsInSingleMetricViewerText}
|
||||
className="results-button"
|
||||
isDisabled={singleMetricEnabled === false || jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInSingleMetricViewerButton"
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
)}
|
||||
<EuiToolTip position="bottom" content={openJobsInAnomalyExplorerText}>
|
||||
<EuiButtonIcon
|
||||
href={createLinkWithUserDefaults('explorer', jobs)}
|
||||
iconType="visTable"
|
||||
aria-label={openJobsInAnomalyExplorerText}
|
||||
className="results-button"
|
||||
isDisabled={jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInAnomalyExplorerButton"
|
||||
/>
|
||||
{isManagementTable ? (
|
||||
<EuiButtonIcon
|
||||
href={`${basePath.get()}/app/ml/${anomalyExplorerLink}`}
|
||||
iconType="visTable"
|
||||
aria-label={openJobsInSingleMetricViewerText}
|
||||
className="results-button"
|
||||
isDisabled={singleMetricEnabled === false || jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInSingleMetricViewerFromManagementButton"
|
||||
/>
|
||||
) : (
|
||||
<Link to={anomalyExplorerLink}>
|
||||
<EuiButtonIcon
|
||||
iconType="visTable"
|
||||
aria-label={openJobsInAnomalyExplorerText}
|
||||
className="results-button"
|
||||
isDisabled={jobActionsDisabled === true}
|
||||
data-test-subj="mlOpenJobsInAnomalyExplorerButton"
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
<div className="actions-border" />
|
||||
</React.Fragment>
|
||||
|
|
|
@ -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) => [
|
||||
'',
|
||||
<EuiLink href={`#/settings/calendars_list/edit_calendar/${c}?_g=()`}>{c}</EuiLink>,
|
||||
<Link to={`/settings/calendars_list/edit_calendar/${c}?_g=()`}>{c}</Link>,
|
||||
]);
|
||||
// remove the calendars list from the general section
|
||||
// so not to show it twice.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
<a key={group} href={getGroupIdsUrl(TAB_IDS.ANOMALY_DETECTION, [group])}>
|
||||
<JobGroup name={group} />
|
||||
</a>
|
||||
);
|
||||
return <AnomalyDetectionJobIdLink key={group} groupId={group} />;
|
||||
}
|
||||
return <JobGroup key={group} name={group} />;
|
||||
})}
|
||||
|
|
|
@ -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 (
|
||||
<EuiLink key={props.groupId} onClick={() => redirectToJobsManagementPage()}>
|
||||
<JobGroup name={props.groupId} />
|
||||
</EuiLink>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiLink key={props.id} onClick={() => redirectToJobsManagementPage()}>
|
||||
{props.id}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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 <EuiLink href={getJobIdUrl(TAB_IDS.ANOMALY_DETECTION, id)}>{id}</EuiLink>;
|
||||
return <AnomalyDetectionJobIdLink key={id} id={id} />;
|
||||
}
|
||||
|
||||
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) => <ResultLinks jobs={[item]} />,
|
||||
render: (item) => <ResultLinks jobs={[item]} isManagementTable={isManagementTable} />,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<EuiButton
|
||||
data-test-subj="mlCreateNewJobButton"
|
||||
|
|
|
@ -394,7 +394,7 @@ export function clearSelectedJobIdFromUrl(url) {
|
|||
url = decodeURIComponent(url);
|
||||
if (url.includes('mlManagement') && (url.includes('jobId') || url.includes('groupIds'))) {
|
||||
const urlParams = getUrlVars(url);
|
||||
const clearedParams = `ml#/jobs?_g=${urlParams._g}`;
|
||||
const clearedParams = `jobs?_g=${urlParams._g}`;
|
||||
window.history.replaceState({}, document.title, clearedParams);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import { PLUGIN_ID } from '../../../../../../../../../../../common/constants/app
|
|||
import { Calendar } from '../../../../../../../../../../../common/types/calendars';
|
||||
import { useMlKibana } from '../../../../../../../../../contexts/kibana';
|
||||
import { GLOBAL_CALENDAR } from '../../../../../../../../../../../common/constants/calendars';
|
||||
import { ML_PAGES } from '../../../../../../../../../../../common/constants/ml_url_generator';
|
||||
|
||||
export const CalendarsSelection: FC = () => {
|
||||
const {
|
||||
|
@ -73,7 +74,7 @@ export const CalendarsSelection: FC = () => {
|
|||
};
|
||||
|
||||
const manageCalendarsHref = getUrlForApp(PLUGIN_ID, {
|
||||
path: '/settings/calendars_list',
|
||||
path: ML_PAGES.CALENDARS_MANAGE,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -39,7 +39,10 @@ import { JobSectionTitle, DatafeedSectionTitle } from './components/common';
|
|||
|
||||
export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) => {
|
||||
const {
|
||||
services: { notifications },
|
||||
services: {
|
||||
notifications,
|
||||
http: { basePath },
|
||||
},
|
||||
} = useMlKibana();
|
||||
|
||||
const navigateToPath = useNavigateToPath();
|
||||
|
@ -108,7 +111,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
|||
jobCreator.end,
|
||||
isSingleMetricJobCreator(jobCreator) === true ? 'timeseriesexplorer' : 'explorer'
|
||||
);
|
||||
window.open(url, '_blank');
|
||||
navigateToPath(`${basePath.get()}/app/ml/${url}`);
|
||||
}
|
||||
|
||||
function clickResetJob() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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."
|
||||
/>
|
||||
<br />
|
||||
<EuiLink href="#jobs/new_job">
|
||||
<EuiLink onClick={onSelectDifferentIndex}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJob.wizard.jobType.selectDifferentIndexLinkText"
|
||||
defaultMessage="Select a different index"
|
||||
|
|
|
@ -21,7 +21,8 @@ import {
|
|||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
import { merge } from 'lodash';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import moment from 'moment';
|
||||
import { useMlKibana, useMlUrlGenerator } from '../../../contexts/kibana';
|
||||
import { ml } from '../../../services/ml_api_service';
|
||||
import { useMlContext } from '../../../contexts/ml';
|
||||
import {
|
||||
|
@ -32,7 +33,6 @@ import {
|
|||
KibanaObjectResponse,
|
||||
ModuleJob,
|
||||
} from '../../../../../common/types/modules';
|
||||
import { mlJobService } from '../../../services/job_service';
|
||||
import { CreateResultCallout } from './components/create_result_callout';
|
||||
import { KibanaObjects } from './components/kibana_objects';
|
||||
import { ModuleJobs } from './components/module_jobs';
|
||||
|
@ -40,6 +40,8 @@ import { checkForSavedObjects } from './resolvers';
|
|||
import { JobSettingsForm, JobSettingsFormValues } from './components/job_settings_form';
|
||||
import { TimeRange } from '../common/components';
|
||||
import { JobId } from '../../../../../common/types/anomaly_detection_jobs';
|
||||
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
|
||||
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
|
||||
|
||||
export interface ModuleJobUI extends ModuleJob {
|
||||
datafeedResult?: DatafeedResponse;
|
||||
|
@ -71,6 +73,8 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
|
|||
const {
|
||||
services: { notifications },
|
||||
} = useMlKibana();
|
||||
const urlGenerator = useMlUrlGenerator();
|
||||
|
||||
// #region State
|
||||
const [jobPrefix, setJobPrefix] = useState<string>('');
|
||||
const [jobs, setJobs] = useState<ModuleJobUI[]>([]);
|
||||
|
@ -185,14 +189,20 @@ export const Page: FC<PageProps> = ({ 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);
|
||||
|
|
|
@ -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<any> {
|
||||
export function checkViewOrCreateJobs(
|
||||
moduleId: string,
|
||||
indexPatternId: string,
|
||||
createLinkWithUserDefaults: CreateLinkWithUserDefaults,
|
||||
navigateToPath: NavigateToPath
|
||||
): Promise<any> {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<I18nContext>
|
||||
<KibanaContextProvider services={{ ...coreStart }}>
|
||||
<KibanaContextProvider services={{ ...coreStart, share }}>
|
||||
<Router history={history}>
|
||||
<EuiPageContent
|
||||
id="kibanaManagementMLSection"
|
||||
|
|
|
@ -13,13 +13,15 @@ import { JobsListPage } from './components';
|
|||
import { getJobsListBreadcrumbs } from '../breadcrumbs';
|
||||
import { setDependencyCache, clearCache } from '../../util/dependency_cache';
|
||||
import './_index.scss';
|
||||
import { SharePluginStart } from '../../../../../../../src/plugins/share/public';
|
||||
|
||||
const renderApp = (
|
||||
element: HTMLElement,
|
||||
history: ManagementAppMountParams['history'],
|
||||
coreStart: CoreStart
|
||||
coreStart: CoreStart,
|
||||
share: SharePluginStart
|
||||
) => {
|
||||
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<MlStartDependencies>,
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<void>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Props> = ({ 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<Props> = ({ 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 (
|
||||
<EuiToolTip position="bottom" content={tooltipText}>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
size="xs"
|
||||
onClick={clickHandler}
|
||||
iconType="visTable"
|
||||
aria-label={viewJobResultsButtonText}
|
||||
className="results-button"
|
||||
data-test-subj="mlOverviewAnalyticsJobViewButton"
|
||||
isDisabled={disabled}
|
||||
>
|
||||
{i18n.translate('xpack.ml.overview.analytics.viewActionName', {
|
||||
defaultMessage: 'View',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
<Link to={viewAnalyticsResultsLink}>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
size="xs"
|
||||
iconType="visTable"
|
||||
aria-label={viewJobResultsButtonText}
|
||||
className="results-button"
|
||||
data-test-subj="mlOverviewAnalyticsJobViewButton"
|
||||
isDisabled={disabled}
|
||||
>
|
||||
{i18n.translate('xpack.ml.overview.analytics.viewActionName', {
|
||||
defaultMessage: 'View',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</Link>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<Props> = ({ jobCreationDisabled }) => {
|
|||
const [errorMessage, setErrorMessage] = useState<any>(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<Props> = ({ jobCreationDisabled }) => {
|
|||
{isInitialized === false && (
|
||||
<EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" />
|
||||
)}
|
||||
|
||||
{errorMessage === undefined && isInitialized === true && analytics.length === 0 && (
|
||||
<EuiEmptyPrompt
|
||||
iconType="createAdvancedJob"
|
||||
|
@ -95,7 +106,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
}
|
||||
actions={
|
||||
<EuiButton
|
||||
href="#/data_frame_analytics?"
|
||||
onClick={redirectToDataFrameAnalyticsManagementPage}
|
||||
color="primary"
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
|
@ -111,6 +122,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
)}
|
||||
{isInitialized === true && analytics.length > 0 && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
|
@ -136,7 +148,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
defaultMessage: 'Refresh',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton size="s" fill href="#/data_frame_analytics?">
|
||||
<EuiButton size="s" fill onClick={redirectToDataFrameAnalyticsManagementPage}>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
|
|
|
@ -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<Props> = ({ jobsList }) => {
|
|||
|
||||
return (
|
||||
<EuiToolTip position="bottom" content={openJobsInAnomalyExplorerText}>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
size="xs"
|
||||
href={createLinkWithUserDefaults('explorer', jobsList)}
|
||||
iconType="visTable"
|
||||
aria-label={openJobsInAnomalyExplorerText}
|
||||
className="results-button"
|
||||
data-test-subj={`openOverviewJobsInAnomalyExplorer`}
|
||||
>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.viewActionName', {
|
||||
defaultMessage: 'View',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
<Link to={createLinkWithUserDefaults('explorer', jobsList)}>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
size="xs"
|
||||
iconType="visTable"
|
||||
aria-label={openJobsInAnomalyExplorerText}
|
||||
className="results-button"
|
||||
data-test-subj={`openOverviewJobsInAnomalyExplorer`}
|
||||
>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.viewActionName', {
|
||||
defaultMessage: 'View',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</Link>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<Group>;
|
||||
|
||||
|
@ -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<Props> = ({ 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<GroupsDictionary>({});
|
||||
const [groupsCount, setGroupsCount] = useState<number>(0);
|
||||
|
@ -157,7 +173,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
return (
|
||||
<EuiPanel className={panelClass}>
|
||||
{typeof errorMessage !== 'undefined' && errorDisplay}
|
||||
{isLoading && <EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" />}
|
||||
{isLoading && <EuiLoadingSpinner className="mlOverviewPanel__spinner" size="xl" />}
|
||||
{isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && (
|
||||
<EuiEmptyPrompt
|
||||
iconType="createSingleMetricJob"
|
||||
|
@ -180,7 +196,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
actions={
|
||||
<EuiButton
|
||||
color="primary"
|
||||
href={createJobLink}
|
||||
onClick={redirectToCreateJobSelectIndexPage}
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
isDisabled={jobCreationDisabled}
|
||||
|
@ -203,7 +219,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
defaultMessage: 'Refresh',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton size="s" fill href="#/jobs?">
|
||||
<EuiButton size="s" fill onClick={redirectToJobsManagementPage}>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
|
||||
defaultMessage: 'Manage jobs',
|
||||
})}
|
||||
|
|
|
@ -88,7 +88,7 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, jobsList, statsBarData
|
|||
<span>
|
||||
{i18n.translate('xpack.ml.overview.anomalyDetection.tableMaxScore', {
|
||||
defaultMessage: 'Max anomaly score',
|
||||
})}{' '}
|
||||
})}
|
||||
<EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" />
|
||||
</span>
|
||||
</EuiToolTip>
|
||||
|
@ -203,6 +203,7 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, jobsList, statsBarData
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -21,13 +21,17 @@ export interface ResolverResults {
|
|||
|
||||
interface BasicResolverDependencies {
|
||||
indexPatterns: IndexPatternsContract;
|
||||
redirectToMlAccessDeniedPage: () => Promise<void>;
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<Route
|
||||
|
|
|
@ -18,11 +18,14 @@ import { Page } from '../../../data_frame_analytics/pages/analytics_creation';
|
|||
import { breadcrumbOnClickFactory, getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
|
||||
import { loadNewJobCapabilities } from '../../../services/new_job_capabilities_service';
|
||||
|
||||
export const analyticsJobsCreationRouteFactory = (navigateToPath: NavigateToPath): MlRoute => ({
|
||||
export const analyticsJobsCreationRouteFactory = (
|
||||
navigateToPath: NavigateToPath,
|
||||
basePath: string
|
||||
): MlRoute => ({
|
||||
path: '/data_frame_analytics/new_job',
|
||||
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
|
||||
breadcrumbs: [
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
|
||||
{
|
||||
text: i18n.translate('xpack.ml.dataFrameAnalyticsBreadcrumbs.dataFrameManagementLabel', {
|
||||
defaultMessage: 'Data Frame Analytics',
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ location, deps }) => {
|
|||
const { context } = useResolver('', undefined, deps.config, basicResolvers(deps));
|
||||
const { _g }: Record<string, any> = 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 (
|
||||
<PageLoader context={context}>
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ location, deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
|
||||
const { context } = useResolver(undefined, undefined, deps.config, {
|
||||
checkBasicLicense,
|
||||
checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
|
||||
checkFindFileStructurePrivilege: () =>
|
||||
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
|
||||
});
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ location, deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
|
||||
const { context } = useResolver('', undefined, deps.config, {
|
||||
checkBasicLicense,
|
||||
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),
|
||||
checkFindFileStructurePrivilege: checkFindFileStructurePrivilegeResolver,
|
||||
checkFindFileStructurePrivilege: () =>
|
||||
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
|
||||
});
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ location, deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
|
||||
ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
|
||||
);
|
||||
|
||||
const { index, savedSearchId }: Record<string, any> = 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 (
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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) => (
|
||||
<PageWrapper
|
||||
|
@ -51,10 +56,13 @@ export const indexOrSearchRouteFactory = (navigateToPath: NavigateToPath): MlRou
|
|||
mode={MODE.NEW_JOB}
|
||||
/>
|
||||
),
|
||||
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) => (
|
||||
<PageWrapper
|
||||
|
@ -64,19 +72,31 @@ export const dataVizIndexOrSearchRouteFactory = (navigateToPath: NavigateToPath)
|
|||
mode={MODE.DATAVISUALIZER}
|
||||
/>
|
||||
),
|
||||
breadcrumbs: getBreadcrumbs(navigateToPath),
|
||||
breadcrumbs: getBreadcrumbs(navigateToPath, basePath),
|
||||
});
|
||||
|
||||
const PageWrapper: FC<IndexOrSearchPageProps> = ({ 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(
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ location, deps }) => {
|
|||
const { id: moduleId, index: indexPatternId }: Record<string, any> = 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;
|
||||
};
|
||||
|
|
|
@ -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) => <PageWrapper {...props} jobType={JOB_TYPE.SINGLE_METRIC} deps={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) => <PageWrapper {...props} jobType={JOB_TYPE.MULTI_METRIC} deps={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) => <PageWrapper {...props} jobType={JOB_TYPE.POPULATION} deps={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) => <PageWrapper {...props} jobType={JOB_TYPE.ADVANCED} deps={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) => <PageWrapper {...props} jobType={JOB_TYPE.CATEGORIZATION} deps={deps} />,
|
||||
breadcrumbs: getCategorizationBreadcrumbs(navigateToPath),
|
||||
breadcrumbs: getCategorizationBreadcrumbs(navigateToPath, basePath),
|
||||
});
|
||||
|
||||
const PageWrapper: FC<WizardPageProps> = ({ location, jobType, deps }) => {
|
||||
const redirectToJobsManagementPage = useCreateAndNavigateToMlLink(
|
||||
ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE
|
||||
);
|
||||
|
||||
const { index, savedSearchId }: Record<string, any> = 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,
|
||||
});
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ 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<PageProps> = ({ deps }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const appRootRouteFactory = (): MlRoute => ({
|
||||
export const appRootRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
|
||||
path: '/',
|
||||
render: () => <Page />,
|
||||
breadcrumbs: [],
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
|
||||
const { context } = useResolver(undefined, undefined, deps.config, {
|
||||
checkFullLicense,
|
||||
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
|
||||
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
|
||||
getMlNodeCount,
|
||||
});
|
||||
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={deps} mode={MODE.NEW} />,
|
||||
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) => <PageWrapper {...props} deps={deps} mode={MODE.EDIT} />,
|
||||
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<NewCalendarPageProps> = ({ 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 });
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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<PageProps> = ({ deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
|
||||
const { context } = useResolver(undefined, undefined, deps.config, {
|
||||
checkFullLicense,
|
||||
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
|
||||
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
|
||||
getMlNodeCount,
|
||||
});
|
||||
|
||||
|
|
|
@ -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) => <PageWrapper {...props} mode={MODE.NEW} deps={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) => <PageWrapper {...props} mode={MODE.EDIT} deps={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<NewFilterPageProps> = ({ 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 });
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={deps} />,
|
||||
breadcrumbs: [
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath),
|
||||
getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath),
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
|
||||
getBreadcrumbWithUrlForApp('SETTINGS_BREADCRUMB', navigateToPath, basePath),
|
||||
],
|
||||
});
|
||||
|
||||
const PageWrapper: FC<PageProps> = ({ deps }) => {
|
||||
const { redirectToMlAccessDeniedPage } = deps;
|
||||
|
||||
const { context } = useResolver(undefined, undefined, deps.config, {
|
||||
checkFullLicense,
|
||||
checkGetJobsCapabilities: checkGetJobsCapabilitiesResolver,
|
||||
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
|
||||
getMlNodeCount,
|
||||
});
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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) => <PageWrapper {...props} deps={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',
|
||||
|
|
|
@ -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<any | null>(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({});
|
||||
|
|
|
@ -797,7 +797,6 @@ function createResultsUrl(jobIds, start, end, resultsPage, mode = 'absolute') {
|
|||
let path = '';
|
||||
|
||||
if (resultsPage !== undefined) {
|
||||
path += '#/';
|
||||
path += resultsPage;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -141,7 +147,7 @@ export const AnomalyDetectionSettings: FC = () => {
|
|||
flush="left"
|
||||
size="l"
|
||||
color="primary"
|
||||
href="#/settings/calendars_list/new_calendar"
|
||||
onClick={redirectToNewCalendarPage}
|
||||
isDisabled={canCreateCalendar === false}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -167,7 +173,7 @@ export const AnomalyDetectionSettings: FC = () => {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.settings.anomalyDetection.filterListsText"
|
||||
defaultMessage=" Filter lists contain values that you can use to include or exclude events from the machine learning analysis."
|
||||
defaultMessage="Filter lists contain values that you can use to include or exclude events from the machine learning analysis."
|
||||
/>
|
||||
</p>
|
||||
</EuiTextColor>
|
||||
|
@ -194,7 +200,7 @@ export const AnomalyDetectionSettings: FC = () => {
|
|||
flush="left"
|
||||
size="l"
|
||||
color="primary"
|
||||
href="#/settings/filter_lists"
|
||||
onClick={redirectToFilterLists}
|
||||
isDisabled={canGetFilters === false}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -208,7 +214,7 @@ export const AnomalyDetectionSettings: FC = () => {
|
|||
data-test-subj="mlFilterListsCreateButton"
|
||||
size="l"
|
||||
color="primary"
|
||||
href="#/settings/filter_lists/new_filter_list"
|
||||
onClick={redirectToNewFilterListPage}
|
||||
isDisabled={canCreateFilter === false}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -88,6 +88,7 @@ exports[`CalendarForm Renders calendar form 1`] = `
|
|||
size="xl"
|
||||
/>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="mlCalendarApplyToAllJobsSwitch"
|
||||
disabled={false}
|
||||
label={
|
||||
|
@ -99,6 +100,68 @@ exports[`CalendarForm Renders calendar form 1`] = `
|
|||
}
|
||||
name="switch"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Jobs"
|
||||
id="xpack.ml.calendarsEdit.calendarForm.jobsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiComboBox
|
||||
async={false}
|
||||
compressed={false}
|
||||
data-test-subj="mlCalendarJobSelection"
|
||||
fullWidth={false}
|
||||
isClearable={true}
|
||||
isDisabled={false}
|
||||
onChange={[MockFunction]}
|
||||
options={Array []}
|
||||
selectedOptions={Array []}
|
||||
singleSelection={false}
|
||||
sortMatchesBy="none"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Groups"
|
||||
id="xpack.ml.calendarsEdit.calendarForm.groupsLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiComboBox
|
||||
async={false}
|
||||
compressed={false}
|
||||
data-test-subj="mlCalendarJobGroupSelection"
|
||||
fullWidth={false}
|
||||
isClearable={true}
|
||||
isDisabled={false}
|
||||
onChange={[MockFunction]}
|
||||
onCreateOption={[MockFunction]}
|
||||
options={Array []}
|
||||
selectedOptions={Array []}
|
||||
singleSelection={false}
|
||||
sortMatchesBy="none"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer
|
||||
size="xl"
|
||||
/>
|
||||
|
@ -137,7 +200,6 @@ exports[`CalendarForm Renders calendar form 1`] = `
|
|||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
href="#/settings/calendars_list"
|
||||
isDisabled={false}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -25,6 +25,8 @@ import { EventsTable } from '../events_table';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
|
||||
import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_create_url';
|
||||
|
||||
function EditHeader({ calendarId, description }) {
|
||||
return (
|
||||
|
@ -81,6 +83,7 @@ export const CalendarForm = ({
|
|||
const error = isNewCalendarIdValid === false && !isEdit ? [msg] : undefined;
|
||||
const saveButtonDisabled =
|
||||
canCreateCalendar === false || saving || !isNewCalendarIdValid || calendarId === '';
|
||||
const redirectToCalendarsManagementPage = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_MANAGE);
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
|
@ -215,7 +218,7 @@ export const CalendarForm = ({
|
|||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton isDisabled={saving} href={'#/settings/calendars_list'}>
|
||||
<EuiButton isDisabled={saving} onClick={redirectToCalendarsManagementPage}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.calendarsEdit.calendarForm.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
|
|
|
@ -8,6 +8,9 @@ import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
|
|||
import React from 'react';
|
||||
import { CalendarForm } from './calendar_form';
|
||||
|
||||
jest.mock('../../../../contexts/kibana/use_create_url', () => ({
|
||||
useCreateAndNavigateToMlLink: jest.fn(),
|
||||
}));
|
||||
const testProps = {
|
||||
calendarId: '',
|
||||
canCreateCalendar: true,
|
||||
|
@ -31,6 +34,7 @@ const testProps = {
|
|||
selectedGroupOptions: [],
|
||||
selectedJobOptions: [],
|
||||
showNewEventModal: jest.fn(),
|
||||
isGlobalCalendar: false,
|
||||
};
|
||||
|
||||
describe('CalendarForm', () => {
|
||||
|
|
|
@ -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 });
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue