From bf96beb3fbea40196d53c2e696cb061c8866c4f2 Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Sat, 28 Sep 2019 17:47:33 +0200 Subject: [PATCH 01/17] [ML] wip job types to react --- .../data_recognizer/data_recognizer.d.ts | 9 +- .../ml/public/jobs/new_job_new/index.ts | 2 + .../new_job_new/pages/job_type/directive.tsx | 65 ++++ .../jobs/new_job_new/pages/job_type/page.tsx | 313 ++++++++++++++++++ .../jobs/new_job_new/pages/job_type/route.ts | 30 ++ 5 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/directive.tsx create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/route.ts diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts index c8a7bba2d189f..f03d632bcb92f 100644 --- a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts +++ b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts @@ -7,9 +7,14 @@ import { FC } from 'react'; import { IndexPattern } from 'ui/index_patterns'; +import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types'; declare const DataRecognizer: FC<{ indexPattern: IndexPattern; - results: any; - className: string; + savedSearch: SavedSearch; + results: { + count: number; + onChange?: Function; + }; + className?: string; }>; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/index.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/index.ts index d3feaf087524c..2366f2c655000 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/index.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/index.ts @@ -6,3 +6,5 @@ import './pages/new_job/route'; import './pages/new_job/directive'; +import './pages/job_type/route'; +import './pages/job_type/directive'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/directive.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/directive.tsx new file mode 100644 index 0000000000000..4ad689a943160 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/directive.tsx @@ -0,0 +1,65 @@ +/* + * 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 ReactDOM from 'react-dom'; + +// @ts-ignore +import { uiModules } from 'ui/modules'; +const module = uiModules.get('apps/ml', ['react']); +import { timefilter } from 'ui/timefilter'; +import { IndexPatterns } from 'ui/index_patterns'; + +import { I18nContext } from 'ui/i18n'; +import { IPrivate } from 'ui/private'; +import { InjectorService } from '../../../../../common/types/angular'; + +import { SearchItemsProvider } from '../../../new_job/utils/new_job_utils'; +import { Page } from './page'; + +import { KibanaContext, KibanaConfigTypeFix } from '../../../../contexts/kibana'; + +module.directive('mlJobTypePage', ($injector: InjectorService) => { + return { + scope: {}, + restrict: 'E', + link: async (scope: ng.IScope, element: ng.IAugmentedJQuery) => { + // remove time picker from top of page + timefilter.disableTimeRangeSelector(); + timefilter.disableAutoRefreshSelector(); + + const indexPatterns = $injector.get('indexPatterns'); + const kbnBaseUrl = $injector.get('kbnBaseUrl'); + const kibanaConfig = $injector.get('config'); + const Private = $injector.get('Private'); + + const createSearchItems = Private(SearchItemsProvider); + const { indexPattern, savedSearch, combinedQuery } = createSearchItems(); + const kibanaContext = { + combinedQuery, + currentIndexPattern: indexPattern, + currentSavedSearch: savedSearch, + indexPatterns, + kbnBaseUrl, + kibanaConfig, + }; + + ReactDOM.render( + + + {React.createElement(Page)} + + , + element[0] + ); + + element.on('$destroy', () => { + ReactDOM.unmountComponentAtNode(element[0]); + scope.$destroy(); + }); + }, + }; +}); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx new file mode 100644 index 0000000000000..08057cf35dc16 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx @@ -0,0 +1,313 @@ +/* + * 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, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPage, + EuiPageBody, + EuiPageContentBody, + EuiTitle, + EuiSpacer, + EuiCallOut, + EuiText, + EuiFlexGrid, + EuiFlexItem, + EuiPanel, + EuiFlexGroup, + EuiLink, + EuiIcon, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { timeBasedIndexCheck } from 'plugins/ml/util/index_utils'; +import { useKibanaContext } from '../../../../contexts/kibana'; +import { DataRecognizer } from '../../../../components/data_recognizer'; + +export const Page: FC = () => { + const kibanaContext = useKibanaContext(); + + const { currentSavedSearch, currentIndexPattern } = kibanaContext; + + const isTimeBasedIndex = timeBasedIndexCheck(currentIndexPattern); + const indexWarningTitle = + !isTimeBasedIndex && currentSavedSearch.id === undefined + ? i18n.translate('xpack.ml.newJob.wizard.jobType.indexPatternNotTimeBasedMessage', { + defaultMessage: 'Index pattern {indexPatternTitle} is not time based', + values: { indexPatternTitle: currentIndexPattern.title }, + }) + : i18n.translate( + 'xpack.ml.newJob.wizard.jobType.indexPatternFromSavedSearchNotTimeBasedMessage', + { + defaultMessage: + '{savedSearchTitle} uses index pattern {indexPatternTitle} which is not time based', + values: { + savedSearchTitle: currentSavedSearch.title, + indexPatternTitle: currentIndexPattern.title, + }, + } + ); + const pageTitleLabel = + currentSavedSearch.id !== undefined + ? i18n.translate('xpack.ml.newJob.wizard.jobType.savedSearchPageTitleLabel', { + defaultMessage: 'saved search {savedSearchTitle}', + values: { savedSearchTitle: currentSavedSearch.title }, + }) + : i18n.translate('xpack.ml.newJob.wizard.jobType.indexPatternPageTitleLabel', { + defaultMessage: 'index pattern {indexPatternTitle}', + values: { indexPatternTitle: currentIndexPattern.title }, + }); + + const recognizerResults = { + count: 0, + }; + + const getUrl = (basePath: string) => { + return currentSavedSearch.id === undefined + ? `${basePath}?index=${currentIndexPattern.id}` + : `${basePath}?savedSearchId=${currentSavedSearch.id}`; + }; + + const jobTypes = [ + { + href: getUrl('#jobs/new_job/single_metric'), + icon: { + type: 'createSingleMetricJob', + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.singleMetricAriaLabel', { + defaultMessage: 'Single metric job', + }), + }, + header: i18n.translate('xpack.ml.newJob.wizard.jobType.singleMetricTitle', { + defaultMessage: 'Single metric', + }), + text: i18n.translate('xpack.ml.newJob.wizard.jobType.singleMetricDescription', { + defaultMessage: 'Detect anomalies in a single time series.', + }), + id: 'mlJobTypeLinkSingleMetricJob', + }, + { + href: getUrl('#jobs/new_job/multi_metric'), + icon: { + type: 'createMultiMetricJob', + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.multiMetricAriaLabel', { + defaultMessage: 'Multi metric job', + }), + }, + header: i18n.translate('xpack.ml.newJob.wizard.jobType.multiMetricTitle', { + defaultMessage: 'Multi metric', + }), + text: i18n.translate('xpack.ml.newJob.wizard.jobType.multiMetricDescription', { + defaultMessage: + 'Detect anomalies in multiple metrics by splitting a time series by a categorical field.', + }), + id: 'mlJobTypeLinkMultiMetricJob', + }, + { + href: getUrl('#jobs/new_job/population'), + icon: { + type: 'createPopulationJob', + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.populationAriaLabel', { + defaultMessage: 'Population job', + }), + }, + header: i18n.translate('xpack.ml.newJob.wizard.jobType.populationTitle', { + defaultMessage: 'Population', + }), + text: i18n.translate('xpack.ml.newJob.wizard.jobType.populationDescription', { + defaultMessage: + 'Detect activity that is unusual compared to the behavior of the population.', + }), + id: 'mlJobTypeLinkPopulationJob', + }, + { + href: getUrl('#jobs/new_job/advanced'), + icon: { + type: 'createAdvancedJob', + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.advancedAriaLabel', { + defaultMessage: 'Advanced job', + }), + }, + header: i18n.translate('xpack.ml.newJob.wizard.jobType.advancedTitle', { + defaultMessage: 'Advanced', + }), + text: i18n.translate('xpack.ml.newJob.wizard.jobType.advancedDescription', { + defaultMessage: + 'Use the full range of options to create a job for more advanced use cases.', + }), + id: 'mlJobTypeLinkAdvancedJob', + }, + ]; + + return ( + + + + +

+ +

+
+ + + {isTimeBasedIndex === false && ( + +

+ +

+ +
+ )} + + + + + +

+ +

+
+ +

+ +

+
+ + + + + {jobTypes.map(({ href, icon, header, text, id }) => ( + + + + + + + + + +

{header}

+
+ +

{text}

+
+
+
+
+
+
+ ))} +
+ + + + + +

+ +

+
+ +

+ +

+
+ + + + + + + + + + + + + +

+ +

+
+ +

+ +

+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/route.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/route.ts new file mode 100644 index 0000000000000..6b52fee032e06 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/route.ts @@ -0,0 +1,30 @@ +/* + * 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 uiRoutes from 'ui/routes'; + +// @ts-ignore +import { getCreateJobBreadcrumbs } from 'plugins/ml/jobs/breadcrumbs'; +// @ts-ignore +import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; +// @ts-ignore +import { checkLicenseExpired } from '../../../../license/check_license'; +import { checkCreateJobsPrivilege } from '../../../../privilege/check_privilege'; +// @ts-ignore +import { loadCurrentIndexPattern, loadCurrentSavedSearch } from '../../../../util/index_utils'; +// @ts-ignore + +uiRoutes.when('/jobs/new_job/step/job_type', { + template: '', + k7Breadcrumbs: getCreateJobBreadcrumbs, + resolve: { + CheckLicense: checkLicenseExpired, + privileges: checkCreateJobsPrivilege, + indexPattern: loadCurrentIndexPattern, + savedSearch: loadCurrentSavedSearch, + checkMlNodesAvailable, + }, +}); From 8f23c19477c969a58b17bd989173cf5ac2487ee9 Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Mon, 30 Sep 2019 10:19:34 +0200 Subject: [PATCH 02/17] [ML] delete angular job_type page --- .../ml/public/jobs/new_job/wizard/index.js | 1 - .../job_type/__tests__/job_type_controller.js | 46 --- .../new_job/wizard/steps/job_type/index.js | 9 - .../wizard/steps/job_type/job_type.html | 274 ------------------ .../steps/job_type/job_type_controller.js | 103 ------- 5 files changed, 433 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/__tests__/job_type_controller.js delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/index.js delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/index.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/index.js index c2fb464630669..61ce488f69014 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/index.js +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/index.js @@ -9,5 +9,4 @@ // SASS TODO: Import wizard.scss instead // import 'plugins/kibana/visualize/wizard/wizard.less'; import './steps/index_or_search'; -import './steps/job_type'; import 'plugins/ml/components/data_recognizer'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/__tests__/job_type_controller.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/__tests__/job_type_controller.js deleted file mode 100644 index f8fd13b2ae36e..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/__tests__/job_type_controller.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -// Import this way to be able to stub/mock functions later on in the tests using sinon. -import * as indexUtils from 'plugins/ml/util/index_utils'; - -describe('ML - Job Type Controller', () => { - beforeEach(() => { - ngMock.module('kibana'); - }); - - it('Initialize Job Type Controller', (done) => { - const stub = sinon.stub(indexUtils, 'timeBasedIndexCheck').callsFake(() => false); - ngMock.inject(function ($rootScope, $controller, $route) { - // Set up the $route current props required for the tests. - $route.current = { - locals: { - indexPattern: { - id: 'test_id', - title: 'test_pattern' - }, - savedSearch: {} - } - }; - - const scope = $rootScope.$new(); - - expect(() => { - $controller('MlNewJobStepJobType', { $scope: scope }); - }).to.not.throwError(); - - expect(scope.indexWarningTitle).to.eql('Index pattern test_pattern is not time based'); - stub.restore(); - done(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/index.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/index.js deleted file mode 100644 index 9a9fa7e73b9f1..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import './job_type_controller'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html deleted file mode 100644 index 1dc3aea215d93..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html +++ /dev/null @@ -1,274 +0,0 @@ - - - -
- -
-

-
- -
-
-
-
- - - - - - - {{indexWarningTitle}} -
-
-

- -
- -

-
-
-
-
-
- -
-
-

-

-

-
-
- -
-
- -
-

-

-
- -
- - - -
- -
-

-

-
-
- - - -
- -
-
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js deleted file mode 100644 index df7768ee9f0c8..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -/* - * Controller for the second step in the Create Job wizard, allowing - * the user to select the type of job they wish to create. - */ - -import uiRoutes from 'ui/routes'; -import { i18n } from '@kbn/i18n'; -import { checkLicenseExpired } from 'plugins/ml/license/check_license'; -import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; -import { getCreateJobBreadcrumbs } from 'plugins/ml/jobs/breadcrumbs'; -import { SearchItemsProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; -import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils'; -import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed'; -import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; -import template from './job_type.html'; -import { timefilter } from 'ui/timefilter'; - -uiRoutes - .when('/jobs/new_job/step/job_type', { - template, - k7Breadcrumbs: getCreateJobBreadcrumbs, - resolve: { - CheckLicense: checkLicenseExpired, - privileges: checkCreateJobsPrivilege, - indexPattern: loadCurrentIndexPattern, - savedSearch: loadCurrentSavedSearch, - checkMlNodesAvailable, - } - }); - - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -module.controller('MlNewJobStepJobType', - function ($scope, Private) { - - timefilter.disableTimeRangeSelector(); // remove time picker from top of page - timefilter.disableAutoRefreshSelector(); // remove time picker from top of page - - const createSearchItems = Private(SearchItemsProvider); - const { - indexPattern, - savedSearch } = createSearchItems(); - - // check to see that the index pattern is time based. - // if it isn't, display a warning and disable all links - $scope.indexWarningTitle = ''; - $scope.isTimeBasedIndex = timeBasedIndexCheck(indexPattern); - if ($scope.isTimeBasedIndex === false) { - $scope.indexWarningTitle = (savedSearch.id === undefined) ? - i18n.translate('xpack.ml.newJob.wizard.jobType.indexPatternNotTimeBasedMessage', { - defaultMessage: 'Index pattern {indexPatternTitle} is not time based', - values: { indexPatternTitle: indexPattern.title } - }) - : i18n.translate('xpack.ml.newJob.wizard.jobType.indexPatternFromSavedSearchNotTimeBasedMessage', { - defaultMessage: '{savedSearchTitle} uses index pattern {indexPatternTitle} which is not time based', - values: { - savedSearchTitle: savedSearch.title, - indexPatternTitle: indexPattern.title - } - }); - } - - $scope.indexPattern = indexPattern; - $scope.savedSearch = savedSearch; - $scope.recognizerResults = { - count: 0, - onChange() { - $scope.$applyAsync(); - } - }; - - $scope.pageTitleLabel = (savedSearch.id !== undefined) ? - i18n.translate('xpack.ml.newJob.wizard.jobType.savedSearchPageTitleLabel', { - defaultMessage: 'saved search {savedSearchTitle}', - values: { savedSearchTitle: savedSearch.title } - }) - : i18n.translate('xpack.ml.newJob.wizard.jobType.indexPatternPageTitleLabel', { - defaultMessage: 'index pattern {indexPatternTitle}', - values: { indexPatternTitle: indexPattern.title } - }); - - $scope.getUrl = function (basePath) { - return (savedSearch.id === undefined) ? `${basePath}?index=${indexPattern.id}` : - `${basePath}?savedSearchId=${savedSearch.id}`; - }; - - $scope.addSelectionToRecentlyAccessed = function () { - const title = (savedSearch.id === undefined) ? indexPattern.title : savedSearch.title; - const url = $scope.getUrl(''); - addItemToRecentlyAccessed('jobs/new_job/datavisualizer', title, url); - }; - - }); From f5acff26ae4da1475018916501ccc8dbb2698678 Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Mon, 30 Sep 2019 12:09:23 +0200 Subject: [PATCH 03/17] [ML] TS refactoring --- .../public/{breadcrumbs.js => breadcrumbs.ts} | 16 +-- .../data_recognizer/data_recognizer.d.ts | 2 +- .../jobs/{breadcrumbs.js => breadcrumbs.ts} | 71 ++++++------ .../jobs/new_job_new/pages/job_type/page.tsx | 62 ++++++---- .../jobs/new_job_new/pages/job_type/route.ts | 5 +- .../jobs/new_job_new/pages/new_job/route.ts | 2 - .../plugins/ml/public/util/index_utils.js | 108 ------------------ .../plugins/ml/public/util/index_utils.ts | 108 ++++++++++++++++++ ...ently_accessed.js => recently_accessed.ts} | 12 +- 9 files changed, 199 insertions(+), 187 deletions(-) rename x-pack/legacy/plugins/ml/public/{breadcrumbs.js => breadcrumbs.ts} (76%) rename x-pack/legacy/plugins/ml/public/jobs/{breadcrumbs.js => breadcrumbs.ts} (55%) delete mode 100644 x-pack/legacy/plugins/ml/public/util/index_utils.js create mode 100644 x-pack/legacy/plugins/ml/public/util/index_utils.ts rename x-pack/legacy/plugins/ml/public/util/{recently_accessed.js => recently_accessed.ts} (80%) diff --git a/x-pack/legacy/plugins/ml/public/breadcrumbs.js b/x-pack/legacy/plugins/ml/public/breadcrumbs.ts similarity index 76% rename from x-pack/legacy/plugins/ml/public/breadcrumbs.js rename to x-pack/legacy/plugins/ml/public/breadcrumbs.ts index bdde734be7c1a..ba4703d4818ff 100644 --- a/x-pack/legacy/plugins/ml/public/breadcrumbs.js +++ b/x-pack/legacy/plugins/ml/public/breadcrumbs.ts @@ -8,28 +8,28 @@ import { i18n } from '@kbn/i18n'; export const ML_BREADCRUMB = Object.freeze({ text: i18n.translate('xpack.ml.machineLearningBreadcrumbLabel', { - defaultMessage: 'Machine Learning' + defaultMessage: 'Machine Learning', }), - href: '#/' + href: '#/', }); export const SETTINGS = Object.freeze({ text: i18n.translate('xpack.ml.settingsBreadcrumbLabel', { - defaultMessage: 'Settings' + defaultMessage: 'Settings', }), - href: '#/settings?' + href: '#/settings?', }); export const ANOMALY_DETECTION_BREADCRUMB = Object.freeze({ text: i18n.translate('xpack.ml.anomalyDetectionBreadcrumbLabel', { - defaultMessage: 'Anomaly Detection' + defaultMessage: 'Anomaly Detection', }), - href: '#/jobs?' + href: '#/jobs?', }); export const DATA_VISUALIZER_BREADCRUMB = Object.freeze({ text: i18n.translate('xpack.ml.datavisualizerBreadcrumbLabel', { - defaultMessage: 'Data Visualizer' + defaultMessage: 'Data Visualizer', }), - href: '#/datavisualizer?' + href: '#/datavisualizer?', }); diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts index f03d632bcb92f..e7d191a31e034 100644 --- a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts +++ b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts @@ -11,7 +11,7 @@ import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/type declare const DataRecognizer: FC<{ indexPattern: IndexPattern; - savedSearch: SavedSearch; + savedSearch?: SavedSearch; results: { count: number; onChange?: Function; diff --git a/x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.js b/x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.ts similarity index 55% rename from x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.js rename to x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.ts index d066a524d70aa..35e9c3326a4cc 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.js +++ b/x-pack/legacy/plugins/ml/public/jobs/breadcrumbs.ts @@ -4,12 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ - -import { ML_BREADCRUMB, DATA_VISUALIZER_BREADCRUMB, ANOMALY_DETECTION_BREADCRUMB } from '../breadcrumbs'; import { i18n } from '@kbn/i18n'; +import { Breadcrumb } from 'ui/chrome'; +import { + ANOMALY_DETECTION_BREADCRUMB, + DATA_VISUALIZER_BREADCRUMB, + ML_BREADCRUMB, +} from '../breadcrumbs'; - -export function getJobManagementBreadcrumbs() { +export function getJobManagementBreadcrumbs(): Breadcrumb[] { // Whilst top level nav menu with tabs remains, // use root ML breadcrumb. return [ @@ -17,93 +20,93 @@ export function getJobManagementBreadcrumbs() { ANOMALY_DETECTION_BREADCRUMB, { text: i18n.translate('xpack.ml.anomalyDetection.jobManagementLabel', { - defaultMessage: 'Job Management' + defaultMessage: 'Job Management', }), - href: '' - } + href: '', + }, ]; } -export function getCreateJobBreadcrumbs() { +export function getCreateJobBreadcrumbs(): Breadcrumb[] { return [ ML_BREADCRUMB, ANOMALY_DETECTION_BREADCRUMB, { text: i18n.translate('xpack.ml.jobsBreadcrumbs.createJobLabel', { - defaultMessage: 'Create job' + defaultMessage: 'Create job', }), - href: '#/jobs/new_job' - } + href: '#/jobs/new_job', + }, ]; } -export function getCreateSingleMetricJobBreadcrumbs() { +export function getCreateSingleMetricJobBreadcrumbs(): Breadcrumb[] { return [ ...getCreateJobBreadcrumbs(), { text: i18n.translate('xpack.ml.jobsBreadcrumbs.singleMetricLabel', { - defaultMessage: 'Single metric' + defaultMessage: 'Single metric', }), - href: '' - } + href: '', + }, ]; } -export function getCreateMultiMetricJobBreadcrumbs() { +export function getCreateMultiMetricJobBreadcrumbs(): Breadcrumb[] { return [ ...getCreateJobBreadcrumbs(), { text: i18n.translate('xpack.ml.jobsBreadcrumbs.multiMetricLabel', { - defaultMessage: 'Multi metric' + defaultMessage: 'Multi metric', }), - href: '' - } + href: '', + }, ]; } -export function getCreatePopulationJobBreadcrumbs() { +export function getCreatePopulationJobBreadcrumbs(): Breadcrumb[] { return [ ...getCreateJobBreadcrumbs(), { text: i18n.translate('xpack.ml.jobsBreadcrumbs.populationLabel', { - defaultMessage: 'Population' + defaultMessage: 'Population', }), - href: '' - } + href: '', + }, ]; } -export function getAdvancedJobConfigurationBreadcrumbs() { +export function getAdvancedJobConfigurationBreadcrumbs(): Breadcrumb[] { return [ ...getCreateJobBreadcrumbs(), { text: i18n.translate('xpack.ml.jobsBreadcrumbs.advancedConfigurationLabel', { - defaultMessage: 'Advanced configuration' + defaultMessage: 'Advanced configuration', }), - href: '' - } + href: '', + }, ]; } -export function getCreateRecognizerJobBreadcrumbs($routeParams) { +export function getCreateRecognizerJobBreadcrumbs($routeParams: any): Breadcrumb[] { return [ ...getCreateJobBreadcrumbs(), { text: $routeParams.id, - href: '' - } + href: '', + }, ]; } -export function getDataVisualizerIndexOrSearchBreadcrumbs() { +export function getDataVisualizerIndexOrSearchBreadcrumbs(): Breadcrumb[] { return [ ML_BREADCRUMB, DATA_VISUALIZER_BREADCRUMB, { text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectIndexOrSearchLabel', { - defaultMessage: 'Select index or search' + defaultMessage: 'Select index or search', }), - href: '' - } + href: '', + }, ]; } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx index 08057cf35dc16..d1525b1ce3950 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { EuiPage, EuiPageBody, - EuiPageContentBody, + EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut, @@ -22,9 +22,10 @@ import { EuiIcon, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { timeBasedIndexCheck } from 'plugins/ml/util/index_utils'; import { useKibanaContext } from '../../../../contexts/kibana'; import { DataRecognizer } from '../../../../components/data_recognizer'; +import { addItemToRecentlyAccessed } from '../../../../util/recently_accessed'; +import { timeBasedIndexCheck } from '../../../../util/index_utils'; export const Page: FC = () => { const kibanaContext = useKibanaContext(); @@ -70,6 +71,15 @@ export const Page: FC = () => { : `${basePath}?savedSearchId=${currentSavedSearch.id}`; }; + const addSelectionToRecentlyAccessed = () => { + const title = + currentSavedSearch.id === undefined ? currentIndexPattern.title : currentSavedSearch.title; + const url = getUrl(''); + addItemToRecentlyAccessed('jobs/new_job/datavisualizer', title, url); + + window.location.href = getUrl('#jobs/new_job/datavisualizer'); + }; + const jobTypes = [ { href: getUrl('#jobs/new_job/single_metric'), @@ -141,9 +151,9 @@ export const Page: FC = () => { ]; return ( - + - +

{ {isTimeBasedIndex === false && ( - -

- -

+ <> + +

+ +

+
+ -
+ )} diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx index 43d2c7f6622b2..2fd1fa8d6a53a 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx @@ -208,11 +208,13 @@ export const Page: FC = () => { - + + + From 0a087e33689c893b13ce608219a2154b2aa77fec Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Tue, 1 Oct 2019 13:24:39 +0200 Subject: [PATCH 14/17] [ML] fix IE issue, IndexPatternSavedObject --- .../create_job_link_card.tsx | 69 +++++++++++-------- .../jobs/new_job_new/pages/job_type/page.tsx | 2 +- .../plugins/ml/public/util/index_utils.ts | 6 +- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx b/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx index a03044bebb953..40f7cd7153578 100644 --- a/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx +++ b/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx @@ -39,32 +39,43 @@ export const CreateJobLinkCard: FC = ({ href, isDisabled, 'data-test-subj': dateTestSubj, -}) => ( - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - - - {typeof icon === 'string' ? ( - - ) : ( - icon - )} - - - -

{title}

-
- -

{description}

-
-
-
-
-
-); +}) => { + const linkHrefAndOnClickProps = { + ...(href ? { href } : {}), + ...(onClick ? { onClick } : {}), + }; + return ( + + + + + {typeof icon === 'string' ? ( + + ) : ( + icon + )} + + + +

{title}

+
+ +

{description}

+
+
+
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx index 2fd1fa8d6a53a..390e43348ce90 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx @@ -174,7 +174,6 @@ export const Page: FC = () => { defaultMessage="Anomaly detection can only be run over indices which are time based." />

-
{ } isDisabled={!isTimeBasedIndex} onClick={addSelectionToRecentlyAccessed} + href={getUrl('#jobs/new_job/datavisualizer')} /> diff --git a/x-pack/legacy/plugins/ml/public/util/index_utils.ts b/x-pack/legacy/plugins/ml/public/util/index_utils.ts index 16a651dae766e..41dd13555726c 100644 --- a/x-pack/legacy/plugins/ml/public/util/index_utils.ts +++ b/x-pack/legacy/plugins/ml/public/util/index_utils.ts @@ -12,10 +12,12 @@ import chrome from 'ui/chrome'; import { SavedSearchLoader } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; import { setup as data } from '../../../../../../src/legacy/core_plugins/data/public/legacy'; -let indexPatternCache: Array> = []; +type IndexPatternSavedObject = SimpleSavedObject; + +let indexPatternCache: IndexPatternSavedObject[] = []; let fullIndexPatterns: IndexPatterns | null = null; -export let refreshIndexPatterns: (() => Promise) | null = null; +export let refreshIndexPatterns: (() => Promise) | null = null; export function loadIndexPatterns() { fullIndexPatterns = data.indexPatterns.indexPatterns; From 6bfbd920b32db2c8a2ee0d28ad4db8e13a38ceef Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Tue, 1 Oct 2019 13:32:54 +0200 Subject: [PATCH 15/17] [ML] fix callout --- .../jobs/new_job_new/pages/job_type/page.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx index 390e43348ce90..4991039ffa288 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/page.tsx @@ -167,13 +167,12 @@ export const Page: FC = () => { {isTimeBasedIndex === false && ( <> - -

- -

+ + +
{ defaultMessage="Learn more about the characteristics of your data and identify the fields for analysis with machine learning." /> } - isDisabled={!isTimeBasedIndex} onClick={addSelectionToRecentlyAccessed} href={getUrl('#jobs/new_job/datavisualizer')} /> From e6848220184c20ab94049ccae2bcb758939d4290 Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Tue, 1 Oct 2019 13:45:49 +0200 Subject: [PATCH 16/17] [ML] job type directive test --- .../pages/job_type/__test__/directive.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts new file mode 100644 index 0000000000000..d7c1b7ec316a6 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts @@ -0,0 +1,45 @@ +/* + * 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 ngMock from 'ng_mock'; +import expect from '@kbn/expect'; +import sinon from 'sinon'; + +// Import this way to be able to stub/mock functions later on in the tests using sinon. +import * as indexUtils from 'plugins/ml/util/index_utils'; + +describe('ML - Job Type Directive', () => { + let $scope; + let $compile; + let $element; + + beforeEach(ngMock.module('kibana')); + beforeEach(() => { + ngMock.inject(function($injector) { + $compile = $injector.get('$compile'); + const $rootScope = $injector.get('$rootScope'); + $scope = $rootScope.$new(); + }); + }); + + afterEach(() => { + $scope.$destroy(); + }); + + it('Initialize Job Type Directive', done => { + const stub = sinon.stub(indexUtils, 'timeBasedIndexCheck').callsFake(() => false); + ngMock.inject(function() { + expect(() => { + $element = $compile('')($scope); + }).to.not.throwError(); + + // directive has scope: false + const scope = $element.isolateScope(); + expect(scope).to.eql(undefined); + done(); + }); + }); +}); From 1c89849fddd8d777c398a10acd2f039ae1c45383 Mon Sep 17 00:00:00 2001 From: Dmitrii Arnautov Date: Tue, 1 Oct 2019 14:34:31 +0200 Subject: [PATCH 17/17] [ML] fix types --- .../create_job_link_card/create_job_link_card.tsx | 1 - .../index_based/components/actions_panel/actions_panel.tsx | 1 + .../pages/job_type/__test__/{directive.ts => directive.js} | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) rename x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/{directive.ts => directive.js} (87%) diff --git a/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx b/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx index 40f7cd7153578..07a924caae772 100644 --- a/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx +++ b/x-pack/legacy/plugins/ml/public/components/create_job_link_card/create_job_link_card.tsx @@ -55,7 +55,6 @@ export const CreateJobLinkCard: FC = ({ }} data-test-subj={dateTestSubj} color="subdued" - type="link" {...linkHrefAndOnClickProps} > diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx index c6488135cfb88..c8295a1e3d8db 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx @@ -87,6 +87,7 @@ export const ActionsPanel: FC = ({ indexPattern }) => { 'Use the full range of options to create a job for more advanced use cases', })} onClick={openAdvancedJobWizard} + href={`${basePath}/app/ml#/jobs/new_job/advanced?index=${indexPattern}`} /> ); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.js similarity index 87% rename from x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts rename to x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.js index d7c1b7ec316a6..5be526f2eb2c0 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/job_type/__test__/directive.js @@ -18,7 +18,7 @@ describe('ML - Job Type Directive', () => { beforeEach(ngMock.module('kibana')); beforeEach(() => { - ngMock.inject(function($injector) { + ngMock.inject(function ($injector) { $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); @@ -30,8 +30,8 @@ describe('ML - Job Type Directive', () => { }); it('Initialize Job Type Directive', done => { - const stub = sinon.stub(indexUtils, 'timeBasedIndexCheck').callsFake(() => false); - ngMock.inject(function() { + sinon.stub(indexUtils, 'timeBasedIndexCheck').callsFake(() => false); + ngMock.inject(function () { expect(() => { $element = $compile('')($scope); }).to.not.throwError();