diff --git a/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/datafeed_service.js b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/datafeed_service.js
new file mode 100644
index 0000000000000..565c61ce6957e
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/datafeed_service.js
@@ -0,0 +1,32 @@
+/*
+ * 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 jobTimePickerTemplate from './job_timepicker_modal.html';
+
+import { uiModules } from 'ui/modules';
+const module = uiModules.get('apps/ml');
+
+module.service('mlDatafeedService', function ($modal) {
+
+ this.openJobTimepickerWindow = function (job) {
+ $modal.open({
+ template: jobTimePickerTemplate,
+ controller: 'MlJobTimepickerModal',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ params: function () {
+ return {
+ job
+ };
+ }
+ }
+ });
+ };
+
+});
diff --git a/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/index.js b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/index.js
new file mode 100644
index 0000000000000..2dea7ffe6dff9
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/index.js
@@ -0,0 +1,12 @@
+/*
+ * 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 './datafeed_service';
+import './job_timepicker_modal_controller';
+import './styles/main.less';
+import 'plugins/ml/jobs/new_job/simple/components/watcher';
diff --git a/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal.html b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal.html
new file mode 100644
index 0000000000000..e9955e4a4b004
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal.html
@@ -0,0 +1,109 @@
+
+
+
Start datafeed for {{jobId}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal_controller.js b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal_controller.js
new file mode 100644
index 0000000000000..ab6351f9b92bd
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/job_timepicker_modal_controller.js
@@ -0,0 +1,142 @@
+/*
+ * 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 moment from 'moment';
+import angular from 'angular';
+
+import { mlJobService } from 'plugins/ml/services/job_service';
+import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
+import { xpackFeatureProvider } from 'plugins/ml/license/check_license';
+
+import { uiModules } from 'ui/modules';
+const module = uiModules.get('apps/ml');
+
+module.controller('MlJobTimepickerModal', function (
+ $scope,
+ $rootScope,
+ $modalInstance,
+ params,
+ Private) {
+ const msgs = mlMessageBarService;
+ $scope.saveLock = false;
+ const xpackFeature = Private(xpackFeatureProvider);
+ $scope.watcherEnabled = xpackFeature.isAvailable('watcher');
+
+ const job = angular.copy(params.job);
+ $scope.jobId = job.job_id;
+
+ $scope.datafeedId = mlJobService.getDatafeedId(job.job_id);
+
+ $scope.start = '';
+ $scope.end = '';
+
+ let lastTime = '';
+ if (job.data_counts && job.data_counts.latest_record_timestamp) {
+ const time = moment(job.data_counts.latest_record_timestamp);
+ lastTime = time.format('YYYY-MM-DD HH:mm:ss');
+ }
+
+ $scope.isNew = (job.data_counts && job.data_counts.input_record_count > 0) ? false : true;
+
+ $scope.ui = {
+ lastTime: lastTime,
+ startDateText: '',
+ startRadio: '1',
+ endDateText: '',
+ endRadio: '1',
+ timepicker: {
+ from: '',
+ to: moment()
+ },
+ setStartRadio: function (i) {
+ $scope.ui.startRadio = i;
+ },
+ createWatch: false
+ };
+
+ function extractForm() {
+ if ($scope.ui.startRadio === '0') {
+ $scope.start = 'now';
+ }
+ else if ($scope.ui.startRadio === '1') {
+ $scope.start = '0';
+ }
+ else if ($scope.ui.startRadio === '2') {
+ $scope.start = moment($scope.ui.timepicker.from).unix() * 1000;
+ }
+
+ if ($scope.ui.endRadio === '0') {
+ $scope.end = undefined;
+ } else if ($scope.ui.endRadio === '1') {
+ $scope.end = moment($scope.ui.timepicker.to).unix() * 1000;
+ }
+ }
+
+ $scope.save = function () {
+ $scope.saveLock = true;
+
+ extractForm();
+
+ let doStartCalled = false;
+ // in 10s call the function to start the datafeed.
+ // if the job has already opened and doStart has already been called, nothing will happen.
+ // However, if the job is still waiting to be opened, the datafeed can be started anyway.
+ window.setTimeout(doStart, 10000);
+
+ // Attempt to open the job first.
+ // If it's already open, ignore the 409 error
+ mlJobService.openJob($scope.jobId)
+ .then(() => {
+ doStart();
+ })
+ .catch((resp) => {
+ if (resp.statusCode === 409) {
+ doStart();
+ } else {
+ if (resp.statusCode === 500) {
+ if (doStartCalled === false) {
+ // doStart hasn't been called yet, this 500 has returned before 10s,
+ // so it's not due to a timeout
+ msgs.error(`Could not open ${$scope.jobId}`, resp);
+ }
+ } else {
+ // console.log(resp);
+ msgs.error(`Could not open ${$scope.jobId}`, resp);
+ }
+ $scope.saveLock = false;
+ }
+ });
+
+ // start the datafeed
+ function doStart() {
+ if (doStartCalled === false) {
+ doStartCalled = true;
+ mlJobService.startDatafeed($scope.datafeedId, $scope.jobId, $scope.start, $scope.end)
+ .then(() => {
+ $rootScope.$broadcast('jobsUpdated');
+
+ if ($scope.ui.createWatch) {
+ $rootScope.$broadcast('openCreateWatchWindow', job);
+ }
+ })
+ .catch(() => {
+ $scope.saveLock = false;
+ });
+ }
+ }
+
+ $modalInstance.close();
+ window.setTimeout(() => {
+ $rootScope.$broadcast('jobsUpdated');
+ }, 500);
+ };
+
+ $scope.cancel = function () {
+ $modalInstance.close();
+ };
+});
diff --git a/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/styles/main.less b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/styles/main.less
new file mode 100644
index 0000000000000..53ee2b22bd73d
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/components/job_timepicker_modal/styles/main.less
@@ -0,0 +1,85 @@
+.job-timepicker-modal {
+ font-size: 14px;
+ padding:20px;
+ cursor: auto;
+
+ h3 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .date_container {
+ width: 200px;
+ display: inline-block;
+ }
+
+ .ml-timepicker-contents {
+ margin-top: 5px;
+
+ .btn-info.active, .kuiButton--primary.active {
+ color: #ffffff;
+ background-color: #154751;
+ border-color: #134049;
+ span {
+ color: #ffffff;
+ }
+ }
+
+ .btn-default, .kuiButton--basic {
+ background: transparent;
+ color: #444444;
+ border: 0px;
+ box-shadow: none;
+ text-shadow: none;
+ }
+
+ [ml-time-input] {
+ text-align: center;
+ }
+
+ label {
+ display: block;
+ }
+ }
+
+ .ml-timepicker-modes {
+ text-transform: capitalize;
+ }
+ .ml-timepicker-section {
+ float: left;
+ padding: 0px 15px;
+ min-width: 294px;
+ width: 294px;
+ border-left: 1px solid #FFFFFF;
+ border-right: 1px solid #FFFFFF;
+
+ .ml-timepicker {
+ padding: 13px;
+ padding-top: none;
+ border: 2px solid #ecf0f1;
+ border-radius: 4px;
+ border-radius: 4px;
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+ border-top: none;
+
+ .btn, .kuiButton {
+ padding-left: 8px;
+ padding-right: 8px;
+ }
+ }
+
+ .ml-timepicker-radio-bottom {
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ }
+ }
+
+ .ml-timepicker-left-border {
+ border-left: 1px solid #ecf0f1;
+ }
+
+ .ml-timepicker-right-border {
+ border-right: 1px solid #ecf0f1;
+ }
+}
diff --git a/x-pack/plugins/ml/public/jobs/new_job/advanced/__tests__/new_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/advanced/__tests__/new_job_controller.js
new file mode 100644
index 0000000000000..11224b9b5f96b
--- /dev/null
+++ b/x-pack/plugins/ml/public/jobs/new_job/advanced/__tests__/new_job_controller.js
@@ -0,0 +1,37 @@
+/*
+ * 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 'expect.js';
+import sinon from 'sinon';
+
+// Import this way to be able to stub/mock `createSearchItems` later on in the test using sinon.
+import * as newJobUtils from 'plugins/ml/jobs/new_job/utils/new_job_utils';
+
+describe('ML - Advanced Job Wizard - New Job Controller', () => {
+ beforeEach(() => {
+ ngMock.module('kibana');
+ });
+
+ it('Initialize New Job Controller', (done) => {
+ sinon.stub(newJobUtils, 'createSearchItems').callsFake(() => ({
+ indexPattern: {},
+ savedSearch: {},
+ combinedQuery: {}
+ }));
+ ngMock.inject(function ($rootScope, $controller) {
+ const scope = $rootScope.$new();
+ $controller('MlNewJob', { $scope: scope });
+
+ // This is just about initializing the controller and making sure
+ // all angularjs based dependencies get loaded without error.
+ // This simple scope test is just a final sanity check.
+ expect(scope.ui.pageTitle).to.be('Create a new job');
+ done();
+ });
+ });
+});
diff --git a/x-pack/plugins/ml/public/jobs/new_job/advanced/index.js b/x-pack/plugins/ml/public/jobs/new_job/advanced/index.js
index 7a578c274b1f6..1e03ccde621df 100644
--- a/x-pack/plugins/ml/public/jobs/new_job/advanced/index.js
+++ b/x-pack/plugins/ml/public/jobs/new_job/advanced/index.js
@@ -12,3 +12,4 @@ import './detectors_list_directive';
import './save_status_modal';
import './field_select_directive';
import 'plugins/ml/components/job_group_select';
+import 'plugins/ml/jobs/components/job_timepicker_modal';