[6.x] [ML] Removing angular services refactor (#18654)

This commit is contained in:
James Gowdy 2018-04-30 15:39:00 +01:00 committed by GitHub
parent 641f9da5af
commit 6f30c7b905
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 3568 additions and 3595 deletions

View file

@ -17,11 +17,7 @@ import 'plugins/ml/factories/listener_factory';
import 'plugins/ml/factories/state_factory'; import 'plugins/ml/factories/state_factory';
import 'plugins/ml/lib/angular_bootstrap_patch'; import 'plugins/ml/lib/angular_bootstrap_patch';
import 'plugins/ml/jobs'; import 'plugins/ml/jobs';
import 'plugins/ml/services/ml_clipboard_service';
import 'plugins/ml/services/job_service';
import 'plugins/ml/services/calendar_service'; import 'plugins/ml/services/calendar_service';
import 'plugins/ml/services/ml_api_service';
import 'plugins/ml/services/results_service';
import 'plugins/ml/components/messagebar'; import 'plugins/ml/components/messagebar';
import 'plugins/ml/datavisualizer'; import 'plugins/ml/datavisualizer';
import 'plugins/ml/explorer'; import 'plugins/ml/explorer';

View file

@ -27,14 +27,16 @@ import {
showTypicalForFunction, showTypicalForFunction,
getSeverity getSeverity
} from 'plugins/ml/util/anomaly_utils'; } from 'plugins/ml/util/anomaly_utils';
import { getFieldTypeFromMapping } from 'plugins/ml/services/mapping_service';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import template from './anomalies_table.html'; import template from './anomalies_table.html';
import 'plugins/ml/components/controls'; import 'plugins/ml/components/controls';
import 'plugins/ml/components/paginated_table'; import 'plugins/ml/components/paginated_table';
import 'plugins/ml/filters/format_value'; import 'plugins/ml/filters/format_value';
import 'plugins/ml/filters/metric_change_description'; import 'plugins/ml/filters/metric_change_description';
import 'plugins/ml/services/job_service';
import 'plugins/ml/services/mapping_service';
import './expanded_row/expanded_row_directive'; import './expanded_row/expanded_row_directive';
import './influencers_cell/influencers_cell_directive'; import './influencers_cell/influencers_cell_directive';
@ -48,13 +50,10 @@ module.directive('mlAnomaliesTable', function (
$window, $window,
$route, $route,
timefilter, timefilter,
mlJobService, Private,
mlESMappingService,
mlResultsService,
mlAnomaliesTableService, mlAnomaliesTableService,
mlSelectIntervalService, mlSelectIntervalService,
mlSelectSeverityService, mlSelectSeverityService,
mlFieldFormatService,
formatValueFilter) { formatValueFilter) {
return { return {
@ -77,6 +76,9 @@ module.directive('mlAnomaliesTable', function (
// just remove these resets. // just remove these resets.
mlSelectIntervalService.state.reset().changed(); mlSelectIntervalService.state.reset().changed();
mlSelectSeverityService.state.reset().changed(); mlSelectSeverityService.state.reset().changed();
const mlResultsService = Private(ResultsServiceProvider);
const mlJobService = Private(JobServiceProvider);
const mlFieldFormatService = Private(FieldFormatServiceProvider);
scope.momentInterval = 'second'; scope.momentInterval = 'second';
@ -195,7 +197,7 @@ module.directive('mlAnomaliesTable', function (
findFieldType(datafeedIndices[i]); findFieldType(datafeedIndices[i]);
function findFieldType(index) { function findFieldType(index) {
mlESMappingService.getFieldTypeFromMapping(index, categorizationFieldName) getFieldTypeFromMapping(index, categorizationFieldName)
.then((resp) => { .then((resp) => {
if (resp !== '') { if (resp !== '') {
createAndOpenUrl(index, resp); createAndOpenUrl(index, resp);

View file

@ -16,7 +16,7 @@ const module = uiModules.get('apps/ml');
module.service('mlConfirmModalService', function ($modal, $q) { module.service('mlConfirmModalService', function ($modal, $q) {
this.open = function (options) { this.open = function (options) {
const deferred = $q.defer(); return $q((resolve, reject) => {
$modal.open({ $modal.open({
template, template,
controller: 'MlConfirmModal', controller: 'MlConfirmModal',
@ -31,13 +31,13 @@ module.service('mlConfirmModalService', function ($modal, $q) {
okLabel: options.okLabel, okLabel: options.okLabel,
cancelLabel: options.cancelLabel, cancelLabel: options.cancelLabel,
hideCancel: options.hideCancel, hideCancel: options.hideCancel,
ok: deferred.resolve, ok: resolve,
cancel: deferred.reject, cancel: reject,
}; };
} }
} }
}); });
return deferred.promise; });
}; };
}); });

View file

@ -11,7 +11,9 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { RecognizedResult } from './recognized_result'; import { RecognizedResult } from './recognized_result';
export function dataRecognizerProvider(ml) { import { ml } from 'plugins/ml/services/ml_api_service';
export function dataRecognizerProvider() {
class DataRecognizer extends Component { class DataRecognizer extends Component {
constructor(props) { constructor(props) {

View file

@ -15,7 +15,6 @@ import _ from 'lodash';
import d3 from 'd3'; import d3 from 'd3';
import moment from 'moment'; import moment from 'moment';
import 'plugins/ml/services/results_service';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
import { numTicksForDateFormat } from 'plugins/ml/util/chart_utils'; import { numTicksForDateFormat } from 'plugins/ml/util/chart_utils';
import { calculateTextWidth } from 'plugins/ml/util/string_utils'; import { calculateTextWidth } from 'plugins/ml/util/string_utils';

View file

@ -6,15 +6,14 @@
import moment from 'moment';
import template from './full_time_range_selector.html'; import template from './full_time_range_selector.html';
import { FieldsServiceProvider } from 'plugins/ml/services/fields_service'; import { FullTimeRangeSelectorServiceProvider } from 'plugins/ml/components/full_time_range_selector/full_time_range_selector_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlFullTimeRangeSelector', function (mlFullTimeRangeSelectorService) { module.directive('mlFullTimeRangeSelector', function (Private) {
return { return {
restrict: 'E', restrict: 'E',
replace: true, replace: true,
@ -25,33 +24,11 @@ module.directive('mlFullTimeRangeSelector', function (mlFullTimeRangeSelectorSer
query: '=' query: '='
}, },
controller: function ($scope) { controller: function ($scope) {
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
$scope.setFullTimeRange = function () { $scope.setFullTimeRange = function () {
mlFullTimeRangeSelectorService.setFullTimeRange($scope.indexPattern, $scope.query); mlFullTimeRangeSelectorService.setFullTimeRange($scope.indexPattern, $scope.query);
}; };
} }
}; };
}) });
.service('mlFullTimeRangeSelectorService', function (
timefilter,
Notifier,
Private) {
const notify = new Notifier();
const fieldsService = Private(FieldsServiceProvider);
// called on button press
this.setFullTimeRange = function (indexPattern, query) {
// load the earliest and latest time stamps for the index
fieldsService.getTimeFieldRange(
indexPattern.title,
indexPattern.timeFieldName,
query)
.then((resp) => {
timefilter.time.from = moment(resp.start.epoch).toISOString();
timefilter.time.to = moment(resp.end.epoch).toISOString();
})
.catch((resp) => {
notify.error(resp);
});
};
});

View file

@ -0,0 +1,36 @@
/*
* 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 { ml } from 'plugins/ml/services/ml_api_service';
export function FullTimeRangeSelectorServiceProvider(timefilter, Notifier, $q) {
const notify = new Notifier();
function setFullTimeRange(indexPattern, query) {
// load the earliest and latest time stamps for the index
$q.when(ml.getTimeFieldRange({
index: indexPattern.title,
timeFieldName: indexPattern.timeFieldName,
query
}))
.then((resp) => {
timefilter.time.from = moment(resp.start.epoch).toISOString();
timefilter.time.to = moment(resp.end.epoch).toISOString();
})
.catch((resp) => {
notify.error(resp);
});
}
return {
setFullTimeRange
};
}

View file

@ -10,11 +10,13 @@ import _ from 'lodash';
import template from './job_group_select.html'; import template from './job_group_select.html';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CalendarServiceProvider } from 'plugins/ml/services/calendar_service';
import { InitAfterBindingsWorkaround } from 'ui/compat'; import { InitAfterBindingsWorkaround } from 'ui/compat';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlJobGroupSelect', function (es, ml, $timeout, mlJobService, mlCalendarService) { module.directive('mlJobGroupSelect', function (es, $timeout, Private) {
return { return {
restrict: 'E', restrict: 'E',
template, template,
@ -26,7 +28,10 @@ module.directive('mlJobGroupSelect', function (es, ml, $timeout, mlJobService, m
controllerAs: 'mlGroupSelect', controllerAs: 'mlGroupSelect',
bindToController: true, bindToController: true,
controller: class MlGroupSelectController extends InitAfterBindingsWorkaround { controller: class MlGroupSelectController extends InitAfterBindingsWorkaround {
initAfterBindings($scope) { initAfterBindings($scope) {
const mlJobService = Private(JobServiceProvider);
const mlCalendarService = Private(CalendarServiceProvider);
this.$scope = $scope; this.$scope = $scope;
this.selectedGroups = []; this.selectedGroups = [];
this.groups = []; this.groups = [];

View file

@ -18,17 +18,19 @@ import d3 from 'd3';
import template from './job_select_list.html'; import template from './job_select_list.html';
import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils'; import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlJobSelectList', function (mlJobService, mlJobSelectService, timefilter) { module.directive('mlJobSelectList', function (Private, mlJobSelectService, timefilter) {
return { return {
restrict: 'AE', restrict: 'AE',
replace: true, replace: true,
transclude: true, transclude: true,
template, template,
controller: function ($scope) { controller: function ($scope) {
const mlJobService = Private(JobServiceProvider);
$scope.jobs = []; $scope.jobs = [];
$scope.groups = []; $scope.groups = [];
$scope.homelessJobs = []; $scope.homelessJobs = [];

View file

@ -11,10 +11,13 @@
import _ from 'lodash'; import _ from 'lodash';
import { notify } from 'ui/notify'; import { notify } from 'ui/notify';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.service('mlJobSelectService', function ($rootScope, mlJobService, globalState) { module.service('mlJobSelectService', function ($rootScope, Private, globalState) {
const mlJobService = Private(JobServiceProvider);
const self = this; const self = this;

View file

@ -9,13 +9,15 @@
import 'ngreact'; import 'ngreact';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']); const module = uiModules.get('apps/ml', ['react']);
import { ValidateJob } from './validate_job_view'; import { ValidateJob } from './validate_job_view';
module.directive('mlValidateJob', function ($injector) { module.directive('mlValidateJob', function ($injector) {
const mlJobService = $injector.get('mlJobService'); const Private = $injector.get('Private');
const mlJobService = Private(JobServiceProvider);
const reactDirective = $injector.get('reactDirective'); const reactDirective = $injector.get('reactDirective');
return reactDirective( return reactDirective(

View file

@ -32,6 +32,7 @@ import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import { createSearchItems } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; import { createSearchItems } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils'; import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './datavisualizer.html'; import template from './datavisualizer.html';
uiRoutes uiRoutes
@ -55,10 +56,10 @@ module
$route, $route,
$timeout, $timeout,
$window, $window,
$q,
Private, Private,
timefilter, timefilter,
AppState, AppState) {
ml) {
timefilter.enableTimeRangeSelector(); timefilter.enableTimeRangeSelector();
timefilter.enableAutoRefreshSelector(); timefilter.enableAutoRefreshSelector();
@ -461,7 +462,7 @@ module
buckets.setBarTarget(BAR_TARGET); buckets.setBarTarget(BAR_TARGET);
const aggInterval = buckets.getInterval(); const aggInterval = buckets.getInterval();
ml.getVisualizerFieldStats({ $q.when(ml.getVisualizerFieldStats({
indexPatternTitle: indexPattern.title, indexPatternTitle: indexPattern.title,
query: $scope.searchQuery, query: $scope.searchQuery,
timeFieldName: indexPattern.timeFieldName, timeFieldName: indexPattern.timeFieldName,
@ -470,7 +471,7 @@ module
samplerShardSize: $scope.samplerShardSize, samplerShardSize: $scope.samplerShardSize,
interval: aggInterval.expression, interval: aggInterval.expression,
fields: numberFields fields: numberFields
}) }))
.then((resp) => { .then((resp) => {
// Add the metric stats to the existing stats in the corresponding card. // Add the metric stats to the existing stats in the corresponding card.
_.each($scope.metricCards, (card) => { _.each($scope.metricCards, (card) => {
@ -520,7 +521,7 @@ module
if (fields.length > 0) { if (fields.length > 0) {
ml.getVisualizerFieldStats({ $q.when(ml.getVisualizerFieldStats({
indexPatternTitle: indexPattern.title, indexPatternTitle: indexPattern.title,
query: $scope.searchQuery, query: $scope.searchQuery,
fields: fields, fields: fields,
@ -529,7 +530,7 @@ module
latest: $scope.latest, latest: $scope.latest,
samplerShardSize: $scope.samplerShardSize, samplerShardSize: $scope.samplerShardSize,
maxExamples: 10 maxExamples: 10
}) }))
.then((resp) => { .then((resp) => {
// Add the metric stats to the existing stats in the corresponding card. // Add the metric stats to the existing stats in the corresponding card.
_.each($scope.fieldCards, (card) => { _.each($scope.fieldCards, (card) => {
@ -575,7 +576,7 @@ module
// 2. List of aggregatable fields that do not exist in docs // 2. List of aggregatable fields that do not exist in docs
// 3. List of non-aggregatable fields that do exist in docs. // 3. List of non-aggregatable fields that do exist in docs.
// 4. List of non-aggregatable fields that do not exist in docs. // 4. List of non-aggregatable fields that do not exist in docs.
ml.getVisualizerOverallStats({ $q.when(ml.getVisualizerOverallStats({
indexPatternTitle: indexPattern.title, indexPatternTitle: indexPattern.title,
query: $scope.searchQuery, query: $scope.searchQuery,
timeFieldName: indexPattern.timeFieldName, timeFieldName: indexPattern.timeFieldName,
@ -584,7 +585,7 @@ module
latest: $scope.latest, latest: $scope.latest,
aggregatableFields: aggregatableFields, aggregatableFields: aggregatableFields,
nonAggregatableFields: nonAggregatableFields nonAggregatableFields: nonAggregatableFields
}) }))
.then((resp) => { .then((resp) => {
$scope.overallStats = resp; $scope.overallStats = resp;
createMetricCards(); createMetricCards();

View file

@ -16,8 +16,10 @@ import _ from 'lodash';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder'; import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder';
import { mlEscape } from 'plugins/ml/util/string_utils'; import { mlEscape } from 'plugins/ml/util/string_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
export function explorerChartConfigBuilder(mlJobService) { export function explorerChartConfigBuilder(Private) {
const mlJobService = Private(JobServiceProvider);
const compiledTooltip = _.template( const compiledTooltip = _.template(
'<div class="explorer-chart-info-tooltip">job ID: <%= jobId %><br/>' + '<div class="explorer-chart-info-tooltip">job ID: <%= jobId %><br/>' +

View file

@ -24,18 +24,19 @@ import { TimeBucketsProvider } from 'ui/time_buckets';
import 'plugins/ml/filters/format_value'; import 'plugins/ml/filters/format_value';
import loadingIndicatorWrapperTemplate from 'plugins/ml/components/loading_indicator/loading_indicator_wrapper.html'; import loadingIndicatorWrapperTemplate from 'plugins/ml/components/loading_indicator/loading_indicator_wrapper.html';
import { mlEscape } from 'plugins/ml/util/string_utils'; import { mlEscape } from 'plugins/ml/util/string_utils';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlExplorerChart', function ( module.directive('mlExplorerChart', function (
Private,
formatValueFilter, formatValueFilter,
mlChartTooltipService, mlChartTooltipService,
mlSelectSeverityService, Private,
mlFieldFormatService) { mlSelectSeverityService) {
function link(scope, element) { function link(scope, element) {
const mlFieldFormatService = Private(FieldFormatServiceProvider);
console.log('ml-explorer-chart directive link series config:', scope.seriesConfig); console.log('ml-explorer-chart directive link series config:', scope.seriesConfig);
if (typeof scope.seriesConfig === 'undefined') { if (typeof scope.seriesConfig === 'undefined') {
// just return so the empty directive renders without an error later on // just return so the empty directive renders without an error later on

View file

@ -21,15 +21,16 @@ const module = uiModules.get('apps/ml');
import { explorerChartConfigBuilder } from './explorer_chart_config_builder'; import { explorerChartConfigBuilder } from './explorer_chart_config_builder';
import { chartLimits } from 'plugins/ml/util/chart_utils'; import { chartLimits } from 'plugins/ml/util/chart_utils';
import { isTimeSeriesViewDetector } from 'plugins/ml/../common/util/job_utils'; import { isTimeSeriesViewDetector } from 'plugins/ml/../common/util/job_utils';
import 'plugins/ml/services/results_service'; import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
module.controller('MlExplorerChartsContainerController', function ($scope, $injector) { module.controller('MlExplorerChartsContainerController', function ($scope, $injector) {
const Private = $injector.get('Private'); const Private = $injector.get('Private');
const mlJobService = $injector.get('mlJobService');
const mlExplorerDashboardService = $injector.get('mlExplorerDashboardService'); const mlExplorerDashboardService = $injector.get('mlExplorerDashboardService');
const mlResultsService = $injector.get('mlResultsService');
const mlSelectSeverityService = $injector.get('mlSelectSeverityService'); const mlSelectSeverityService = $injector.get('mlSelectSeverityService');
const $q = $injector.get('$q'); const $q = $injector.get('$q');
const mlResultsService = Private(ResultsServiceProvider);
const mlJobService = Private(JobServiceProvider);
$scope.seriesToPlot = []; $scope.seriesToPlot = [];

View file

@ -20,9 +20,6 @@ import moment from 'moment';
import 'plugins/ml/components/anomalies_table'; import 'plugins/ml/components/anomalies_table';
import 'plugins/ml/components/influencers_list'; import 'plugins/ml/components/influencers_list';
import 'plugins/ml/components/job_select_list'; import 'plugins/ml/components/job_select_list';
import 'plugins/ml/services/field_format_service';
import 'plugins/ml/services/job_service';
import 'plugins/ml/services/results_service';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
@ -34,6 +31,9 @@ import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import { getIndexPatterns } from 'plugins/ml/util/index_utils'; import { getIndexPatterns } from 'plugins/ml/util/index_utils';
import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher'; import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher';
import { IntervalHelperProvider, getBoundsRoundedToInterval } from 'plugins/ml/util/ml_time_buckets'; import { IntervalHelperProvider, getBoundsRoundedToInterval } from 'plugins/ml/util/ml_time_buckets';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
uiRoutes uiRoutes
.when('/explorer/?', { .when('/explorer/?', {
@ -56,9 +56,6 @@ module.controller('MlExplorerController', function (
Private, Private,
timefilter, timefilter,
mlCheckboxShowChartsService, mlCheckboxShowChartsService,
mlFieldFormatService,
mlJobService,
mlResultsService,
mlJobSelectService, mlJobSelectService,
mlExplorerDashboardService, mlExplorerDashboardService,
mlSelectLimitService, mlSelectLimitService,
@ -72,6 +69,9 @@ module.controller('MlExplorerController', function (
const TimeBuckets = Private(IntervalHelperProvider); const TimeBuckets = Private(IntervalHelperProvider);
const queryFilter = Private(FilterBarQueryFilterProvider); const queryFilter = Private(FilterBarQueryFilterProvider);
const mlResultsService = Private(ResultsServiceProvider);
const mlJobService = Private(JobServiceProvider);
const mlFieldFormatService = Private(FieldFormatServiceProvider);
let resizeTimeout = null; let resizeTimeout = null;

View file

@ -12,8 +12,10 @@ import { parseInterval } from 'ui/utils/parse_interval';
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
import { replaceTokensInUrlValue } from 'plugins/ml/util/custom_url_utils'; import { replaceTokensInUrlValue } from 'plugins/ml/util/custom_url_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
export function CustomUrlEditorServiceProvider(es, mlJobService, $q) { export function CustomUrlEditorServiceProvider(es, Private, $q) {
const mlJobService = Private(JobServiceProvider);
// Builds the full URL for testing out a custom URL configuration, which // Builds the full URL for testing out a custom URL configuration, which
// may contain dollar delimited partition / influencer entity tokens and // may contain dollar delimited partition / influencer entity tokens and

View file

@ -5,11 +5,13 @@
*/ */
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.controller('MlCreateWatchModal', function ($scope, $modalInstance, params, mlMessageBarService, mlCreateWatchService) { module.controller('MlCreateWatchModal', function ($scope, $modalInstance, params, mlMessageBarService, Private) {
const mlCreateWatchService = Private(CreateWatchServiceProvider);
const msgs = mlMessageBarService; // set a reference to the message bar service const msgs = mlMessageBarService; // set a reference to the message bar service
msgs.clear(); msgs.clear();

View file

@ -17,6 +17,7 @@ import { parseInterval } from 'plugins/ml/../common/util/parse_interval';
import { CustomUrlEditorServiceProvider } from 'plugins/ml/jobs/components/custom_url_editor/custom_url_editor_service'; import { CustomUrlEditorServiceProvider } from 'plugins/ml/jobs/components/custom_url_editor/custom_url_editor_service';
import { isWebUrl } from 'plugins/ml/util/string_utils'; import { isWebUrl } from 'plugins/ml/util/string_utils';
import { newJobLimits } from 'plugins/ml/jobs/new_job/utils/new_job_defaults'; import { newJobLimits } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
@ -28,10 +29,10 @@ module.controller('MlEditJobModal', function (
$window, $window,
params, params,
Private, Private,
mlJobService,
mlMessageBarService) { mlMessageBarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
msgs.clear(); msgs.clear();
const mlJobService = Private(JobServiceProvider);
$scope.saveLock = false; $scope.saveLock = false;
const refreshJob = params.pscope.refreshJob; const refreshJob = params.pscope.refreshJob;

View file

@ -7,9 +7,11 @@
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { toLocaleString, detectorToString } from 'plugins/ml/util/string_utils'; import { toLocaleString, detectorToString } from 'plugins/ml/util/string_utils';
import { copyTextToClipboard } from 'plugins/ml/util/clipboard_utils';
import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states'; import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states';
import { ML_DATA_PREVIEW_COUNT } from 'plugins/ml/../common/util/job_utils'; import { ML_DATA_PREVIEW_COUNT } from 'plugins/ml/../common/util/job_utils';
import { checkPermission } from 'plugins/ml/privilege/check_privilege'; import { checkPermission } from 'plugins/ml/privilege/check_privilege';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import numeral from '@elastic/numeral'; import numeral from '@elastic/numeral';
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import angular from 'angular'; import angular from 'angular';
@ -18,7 +20,7 @@ import template from './expanded_row.html';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlJobListExpandedRow', function ($location, mlMessageBarService, mlJobService, mlClipboardService) { module.directive('mlJobListExpandedRow', function ($location, mlMessageBarService, Private) {
return { return {
restrict: 'AE', restrict: 'AE',
replace: false, replace: false,
@ -34,6 +36,7 @@ module.directive('mlJobListExpandedRow', function ($location, mlMessageBarServic
template, template,
link: function ($scope, $element) { link: function ($scope, $element) {
const msgs = mlMessageBarService; // set a reference to the message bar service const msgs = mlMessageBarService; // set a reference to the message bar service
const mlJobService = Private(JobServiceProvider);
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
const DATA_FORMAT = '0.0 b'; const DATA_FORMAT = '0.0 b';
@ -169,7 +172,7 @@ module.directive('mlJobListExpandedRow', function ($location, mlMessageBarServic
$scope.copyToClipboard = function (job) { $scope.copyToClipboard = function (job) {
const newJob = angular.copy(job); const newJob = angular.copy(job);
const success = mlClipboardService.copy(angular.toJson(newJob)); const success = copyTextToClipboard(angular.toJson(newJob));
if (success) { if (success) {
// flash the background color of the json box // flash the background color of the json box
// to show the contents has been copied. // to show the contents has been copied.

View file

@ -8,7 +8,8 @@
import 'ngreact'; import 'ngreact';
import 'plugins/ml/services/forecast_service';
import { ForecastServiceProvider } from 'plugins/ml/services/forecast_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']); const module = uiModules.get('apps/ml', ['react']);
@ -16,7 +17,8 @@ const module = uiModules.get('apps/ml', ['react']);
import { ForecastsTable } from './forecasts_table'; import { ForecastsTable } from './forecasts_table';
module.directive('mlForecastsTable', function ($injector) { module.directive('mlForecastsTable', function ($injector) {
const mlForecastService = $injector.get('mlForecastService'); const Private = $injector.get('Private');
const mlForecastService = Private(ForecastServiceProvider);
const reactDirective = $injector.get('reactDirective'); const reactDirective = $injector.get('reactDirective');
return reactDirective( return reactDirective(

View file

@ -16,4 +16,3 @@ import './expanded_row';
import 'ui/directives/confirm_click'; import 'ui/directives/confirm_click';
import 'plugins/ml/components/paginated_table'; import 'plugins/ml/components/paginated_table';
import 'plugins/ml/components/validate_job'; import 'plugins/ml/components/validate_job';
import 'plugins/ml/services/notification_service';

View file

@ -9,6 +9,9 @@
import moment from 'moment'; import moment from 'moment';
import angular from 'angular'; import angular from 'angular';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
@ -17,10 +20,11 @@ module.controller('MlJobTimepickerModal', function (
$rootScope, $rootScope,
$modalInstance, $modalInstance,
params, params,
mlJobService, Private,
mlCreateWatchService,
mlMessageBarService) { mlMessageBarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
const mlJobService = Private(JobServiceProvider);
const mlCreateWatchService = Private(CreateWatchServiceProvider);
$scope.saveLock = false; $scope.saveLock = false;
$scope.watcherEnabled = mlCreateWatchService.isWatcherEnabled(); $scope.watcherEnabled = mlCreateWatchService.isWatcherEnabled();

View file

@ -16,6 +16,7 @@ import jobsListControlsHtml from './jobs_list_controls.html';
import jobsListArrow from 'plugins/ml/components/paginated_table/open.html'; import jobsListArrow from 'plugins/ml/components/paginated_table/open.html';
import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils'; import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
import { toLocaleString, mlEscape } from 'plugins/ml/util/string_utils'; import { toLocaleString, mlEscape } from 'plugins/ml/util/string_utils';
import { copyTextToClipboard } from 'plugins/ml/util/clipboard_utils';
import uiRoutes from 'ui/routes'; import uiRoutes from 'ui/routes';
import { checkLicense } from 'plugins/ml/license/check_license'; import { checkLicense } from 'plugins/ml/license/check_license';
@ -31,6 +32,9 @@ import createWatchTemplate from 'plugins/ml/jobs/jobs_list/create_watch_modal/cr
import { buttonsEnabledChecks } from 'plugins/ml/jobs/jobs_list/buttons_enabled_checks'; import { buttonsEnabledChecks } from 'plugins/ml/jobs/jobs_list/buttons_enabled_checks';
import { cloudServiceProvider } from 'plugins/ml/services/cloud_service'; import { cloudServiceProvider } from 'plugins/ml/services/cloud_service';
import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults'; import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CalendarServiceProvider } from 'plugins/ml/services/calendar_service';
import { JobMessagesServiceProvider } from 'plugins/ml/services/job_messages_service';
uiRoutes uiRoutes
.when('/jobs/?', { .when('/jobs/?', {
@ -60,11 +64,7 @@ module.controller('MlJobsList',
kbnUrl, kbnUrl,
Private, Private,
mlMessageBarService, mlMessageBarService,
mlClipboardService, mlDatafeedService) {
mlJobService,
mlCalendarService,
mlDatafeedService,
mlNotificationService) {
timefilter.disableTimeRangeSelector(); // remove time picker from top of page timefilter.disableTimeRangeSelector(); // remove time picker from top of page
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page timefilter.disableAutoRefreshSelector(); // remove time picker from top of page
@ -85,6 +85,9 @@ module.controller('MlJobsList',
$scope.mlNodesAvailable = mlNodesAvailable(); $scope.mlNodesAvailable = mlNodesAvailable();
$scope.permissionToViewMlNodeCount = permissionToViewMlNodeCount(); $scope.permissionToViewMlNodeCount = permissionToViewMlNodeCount();
const mlJobService = Private(JobServiceProvider);
const mlCalendarService = Private(CalendarServiceProvider);
const jobMessagesService = Private(JobMessagesServiceProvider);
const { isRunningOnCloud, getCloudId } = Private(cloudServiceProvider); const { isRunningOnCloud, getCloudId } = Private(cloudServiceProvider);
$scope.isCloud = isRunningOnCloud(); $scope.isCloud = isRunningOnCloud();
$scope.cloudId = getCloudId(); $scope.cloudId = getCloudId();
@ -169,7 +172,7 @@ module.controller('MlJobsList',
}; };
$scope.copyToClipboard = function (job) { $scope.copyToClipboard = function (job) {
const success = mlClipboardService.copy(angular.toJson(job)); const success = copyTextToClipboard(angular.toJson(job));
if (success) { if (success) {
msgs.clear(); msgs.clear();
msgs.info(job.job_id + ' JSON copied to clipboard'); msgs.info(job.job_id + ' JSON copied to clipboard');
@ -436,7 +439,7 @@ module.controller('MlJobsList',
} }
} }
return mlNotificationService.getJobAuditMessages(fromRange, jobId) return jobMessagesService.getJobAuditMessages(fromRange, jobId)
.then((resp) => { .then((resp) => {
const messages = resp.messages; const messages = resp.messages;
_.each(messages, (msg) => { _.each(messages, (msg) => {
@ -488,7 +491,7 @@ module.controller('MlJobsList',
createTimes[job.job_id] = moment(job.create_time).valueOf(); createTimes[job.job_id] = moment(job.create_time).valueOf();
}); });
mlNotificationService.getAuditMessagesSummary() jobMessagesService.getAuditMessagesSummary()
.then((resp) => { .then((resp) => {
const messagesPerJob = resp.messagesPerJob; const messagesPerJob = resp.messagesPerJob;
_.each(messagesPerJob, (job) => { _.each(messagesPerJob, (job) => {

View file

@ -12,7 +12,7 @@ import angular from 'angular';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.controller('MlDetectorFilterModal', function ($scope, $modalInstance, params, mlJobService, mlMessageBarService) { module.controller('MlDetectorFilterModal', function ($scope, $modalInstance, params, mlMessageBarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
msgs.clear(); msgs.clear();
$scope.title = 'Add new filter'; $scope.title = 'Add new filter';

View file

@ -16,11 +16,12 @@ import { detectorToString } from 'plugins/ml/util/string_utils';
import template from './detectors_list.html'; import template from './detectors_list.html';
import detectorModalTemplate from 'plugins/ml/jobs/new_job/advanced/detector_modal/detector_modal.html'; import detectorModalTemplate from 'plugins/ml/jobs/new_job/advanced/detector_modal/detector_modal.html';
import detectorFilterModalTemplate from 'plugins/ml/jobs/new_job/advanced/detector_filter_modal/detector_filter_modal.html'; import detectorFilterModalTemplate from 'plugins/ml/jobs/new_job/advanced/detector_filter_modal/detector_filter_modal.html';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlJobDetectorsList', function ($modal, $q, mlJobService) { module.directive('mlJobDetectorsList', function ($modal, $q, Private) {
return { return {
restrict: 'AE', restrict: 'AE',
replace: true, replace: true,
@ -33,6 +34,7 @@ module.directive('mlJobDetectorsList', function ($modal, $q, mlJobService) {
}, },
template, template,
controller: function ($scope) { controller: function ($scope) {
const mlJobService = Private(JobServiceProvider);
$scope.addDetector = function (dtr, index) { $scope.addDetector = function (dtr, index) {
if (dtr !== undefined) { if (dtr !== undefined) {

View file

@ -18,7 +18,7 @@ import { checkLicense } from 'plugins/ml/license/check_license';
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import template from './new_job.html'; import template from './new_job.html';
import saveStatusTemplate from 'plugins/ml/jobs/new_job/advanced/save_status_modal/save_status_modal.html'; import saveStatusTemplate from 'plugins/ml/jobs/new_job/advanced/save_status_modal/save_status_modal.html';
import { createSearchItems } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; import { createSearchItems, createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { getIndexPatterns, getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils'; import { getIndexPatterns, getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
import { ML_JOB_FIELD_TYPES, ES_FIELD_TYPES } from 'plugins/ml/../common/constants/field_types'; import { ML_JOB_FIELD_TYPES, ES_FIELD_TYPES } from 'plugins/ml/../common/constants/field_types';
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
@ -28,6 +28,8 @@ import {
ML_DATA_PREVIEW_COUNT, ML_DATA_PREVIEW_COUNT,
basicJobValidation basicJobValidation
} from 'plugins/ml/../common/util/job_utils'; } from 'plugins/ml/../common/util/job_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { ml } from 'plugins/ml/services/ml_api_service';
uiRoutes uiRoutes
.when('/jobs/new_job/advanced', { .when('/jobs/new_job/advanced', {
@ -65,19 +67,15 @@ module.controller('MlNewJob',
$location, $location,
$modal, $modal,
$q, $q,
$timeout,
courier, courier,
es, es,
ml,
Private, Private,
timefilter, timefilter,
esServerUrl,
mlJobService,
mlMessageBarService, mlMessageBarService,
mlDatafeedService, mlDatafeedService,
mlConfirmModalService) { mlConfirmModalService) {
const mlJobService = Private(JobServiceProvider);
timefilter.disableTimeRangeSelector(); // remove time picker from top of page timefilter.disableTimeRangeSelector(); // remove time picker from top of page
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page timefilter.disableAutoRefreshSelector(); // remove time picker from top of page
const MODE = { const MODE = {
@ -433,12 +431,6 @@ module.controller('MlNewJob',
} }
} }
function createJobForSaving(job) {
const newJob = angular.copy(job);
delete newJob.datafeed_config;
return newJob;
}
function saveFunc() { function saveFunc() {
if ($scope.ui.useDedicatedIndex) { if ($scope.ui.useDedicatedIndex) {

View file

@ -9,13 +9,12 @@
import template from './bucket_span_estimator.html'; import template from './bucket_span_estimator.html';
import { getQueryFromSavedSearch } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; import { getQueryFromSavedSearch } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general'; import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general';
import { ml } from 'plugins/ml/services/ml_api_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlBucketSpanEstimator', function ($injector) { module.directive('mlBucketSpanEstimator', function ($q) {
const ml = $injector.get('ml');
return { return {
restrict: 'AE', restrict: 'AE',
replace: false, replace: false,
@ -79,7 +78,7 @@ module.directive('mlBucketSpanEstimator', function ($injector) {
}); });
} }
ml.estimateBucketSpan(data) $q.when(ml.estimateBucketSpan(data))
.then((interval) => { .then((interval) => {
if (interval.error) { if (interval.error) {
errorHandler(interval.message); errorHandler(interval.message);

View file

@ -6,12 +6,14 @@
import { PostSaveServiceProvider } from './post_save_service';
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
import template from './post_save_options.html'; import template from './post_save_options.html';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlPostSaveOptions', function (mlPostSaveService, mlCreateWatchService) { module.directive('mlPostSaveOptions', function (Private) {
return { return {
restrict: 'AE', restrict: 'AE',
replace: false, replace: false,
@ -23,13 +25,16 @@ module.directive('mlPostSaveOptions', function (mlPostSaveService, mlCreateWatch
template, template,
link: function ($scope) { link: function ($scope) {
$scope.watcherEnabled = mlCreateWatchService.isWatcherEnabled(); const postSaveService = Private(PostSaveServiceProvider);
$scope.status = mlPostSaveService.status; const createWatchService = Private(CreateWatchServiceProvider);
$scope.STATUS = mlPostSaveService.STATUS;
mlCreateWatchService.reset(); $scope.watcherEnabled = createWatchService.isWatcherEnabled();
$scope.status = postSaveService.status;
$scope.STATUS = postSaveService.STATUS;
mlCreateWatchService.config.includeInfluencers = $scope.includeInfluencers; createWatchService.reset();
createWatchService.config.includeInfluencers = $scope.includeInfluencers;
$scope.runInRealtime = false; $scope.runInRealtime = false;
$scope.createWatch = false; $scope.createWatch = false;
$scope.embedded = true; $scope.embedded = true;
@ -39,55 +44,8 @@ module.directive('mlPostSaveOptions', function (mlPostSaveService, mlCreateWatch
}; };
$scope.apply = function () { $scope.apply = function () {
mlPostSaveService.apply($scope.jobId, $scope.runInRealtime, $scope.createWatch); postSaveService.apply($scope.jobId, $scope.runInRealtime, $scope.createWatch);
}; };
} }
}; };
}).service('mlPostSaveService', function (mlJobService, mlMessageBarService, $q, mlCreateWatchService) {
const msgs = mlMessageBarService;
this.STATUS = {
SAVE_FAILED: -1,
SAVING: 0,
SAVED: 1,
};
this.status = {
realtimeJob: null,
watch: null
};
mlCreateWatchService.status = this.status;
this.externalCreateWatch;
this.startRealtimeJob = function (jobId) {
const deferred = $q.defer();
this.status.realtimeJob = this.STATUS.SAVING;
const datafeedId = mlJobService.getDatafeedId(jobId);
mlJobService.openJob(jobId)
.finally(() => {
mlJobService.startDatafeed(datafeedId, jobId, 0, undefined)
.then(() => {
this.status.realtimeJob = this.STATUS.SAVED;
deferred.resolve();
}).catch((resp) => {
msgs.error('Could not start datafeed: ', resp);
this.status.realtimeJob = this.STATUS.SAVE_FAILED;
deferred.reject();
});
});
return deferred.promise;
};
this.apply = function (jobId, runInRealtime, createWatch) {
if (runInRealtime) {
this.startRealtimeJob(jobId)
.then(() => {
if (createWatch) {
mlCreateWatchService.createNewWatch(jobId);
}
});
}
};
}); });

View file

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
export function PostSaveServiceProvider(Private, mlMessageBarService, $q) {
const msgs = mlMessageBarService;
const mlJobService = Private(JobServiceProvider);
const createWatchService = Private(CreateWatchServiceProvider);
class PostSaveService {
constructor() {
this.STATUS = {
SAVE_FAILED: -1,
SAVING: 0,
SAVED: 1,
};
this.status = {
realtimeJob: null,
watch: null
};
createWatchService.status = this.status;
this.externalCreateWatch;
}
startRealtimeJob(jobId) {
return $q((resolve, reject) => {
this.status.realtimeJob = this.STATUS.SAVING;
const datafeedId = mlJobService.getDatafeedId(jobId);
mlJobService.openJob(jobId)
.finally(() => {
mlJobService.startDatafeed(datafeedId, jobId, 0, undefined)
.then(() => {
this.status.realtimeJob = this.STATUS.SAVED;
resolve();
}).catch((resp) => {
msgs.error('Could not start datafeed: ', resp);
this.status.realtimeJob = this.STATUS.SAVE_FAILED;
reject();
});
});
});
}
apply(jobId, runInRealtime, createWatch) {
if (runInRealtime) {
this.startRealtimeJob(jobId)
.then(() => {
if (createWatch) {
createWatchService.createNewWatch(jobId);
}
});
}
}
}
return new PostSaveService();
}

View file

@ -10,7 +10,9 @@
// based on the cardinality of the field being used to split the data. // based on the cardinality of the field being used to split the data.
// the limit should be 10MB plus 20kB per series, rounded up to the nearest MB. // the limit should be 10MB plus 20kB per series, rounded up to the nearest MB.
export function CalculateModelMemoryLimitProvider(ml) { import { ml } from 'plugins/ml/services/ml_api_service';
export function CalculateModelMemoryLimitProvider() {
return function calculateModelMemoryLimit( return function calculateModelMemoryLimit(
indexPattern, indexPattern,
splitFieldName, splitFieldName,

View file

@ -11,9 +11,13 @@
import _ from 'lodash'; import _ from 'lodash';
import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets'; import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
import { calculateTextWidth } from 'plugins/ml/util/string_utils'; import { calculateTextWidth } from 'plugins/ml/util/string_utils';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { SimpleJobSearchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/utils/search_service';
export function ChartDataUtilsProvider($q, Private, timefilter, mlSimpleJobSearchService, mlResultsService) { export function ChartDataUtilsProvider($q, Private, timefilter) {
const TimeBuckets = Private(IntervalHelperProvider); const TimeBuckets = Private(IntervalHelperProvider);
const mlResultsService = Private(ResultsServiceProvider);
const mlSimpleJobSearchService = Private(SimpleJobSearchServiceProvider);
function loadDocCountData(formConfig, chartData) { function loadDocCountData(formConfig, chartData) {
return $q((resolve, reject) => { return $q((resolve, reject) => {

View file

@ -11,13 +11,10 @@ import _ from 'lodash';
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils'; import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils';
import { uiModules } from 'ui/modules'; export function SimpleJobSearchServiceProvider($q, es) {
const module = uiModules.get('apps/ml');
module.service('mlSimpleJobSearchService', function ($q, es) {
// detector swimlane search // detector swimlane search
this.getScoresByRecord = function (jobId, earliestMs, latestMs, interval, firstSplitField) { function getScoresByRecord(jobId, earliestMs, latestMs, interval, firstSplitField) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
results: {} results: {}
@ -116,16 +113,16 @@ module.service('mlSimpleJobSearchService', function ($q, es) {
obj.results[dtrIndex] = dtrResults; obj.results[dtrIndex] = dtrResults;
}); });
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
this.getCategoryFields = function (index, field, size, query) { function getCategoryFields(index, field, size, query) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
results: {} results: {}
@ -153,13 +150,18 @@ module.service('mlSimpleJobSearchService', function ($q, es) {
obj.results.values.push(f.key); obj.results.values.push(f.key);
}); });
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}
return {
getScoresByRecord,
getCategoryFields
}; };
}); }

View file

@ -8,13 +8,15 @@
import _ from 'lodash'; import _ from 'lodash';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
import template from './create_watch.html'; import template from './create_watch.html';
import { ml } from 'plugins/ml/services/ml_api_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.directive('mlCreateWatch', function (es, ml, mlCreateWatchService) { module.directive('mlCreateWatch', function (es, $q, Private) {
return { return {
restrict: 'AE', restrict: 'AE',
replace: false, replace: false,
@ -25,7 +27,7 @@ module.directive('mlCreateWatch', function (es, ml, mlCreateWatchService) {
}, },
template, template,
link: function ($scope) { link: function ($scope) {
const mlCreateWatchService = Private(CreateWatchServiceProvider);
$scope.config = mlCreateWatchService.config; $scope.config = mlCreateWatchService.config;
$scope.status = mlCreateWatchService.status; $scope.status = mlCreateWatchService.status;
$scope.STATUS = mlCreateWatchService.STATUS; $scope.STATUS = mlCreateWatchService.STATUS;
@ -56,7 +58,7 @@ module.directive('mlCreateWatch', function (es, ml, mlCreateWatchService) {
} }
// load elasticsearch settings to see if email has been configured // load elasticsearch settings to see if email has been configured
ml.getNotificationSettings().then((resp) => { $q.when(ml.getNotificationSettings()).then((resp) => {
if (_.has(resp, 'defaults.xpack.notification.email')) { if (_.has(resp, 'defaults.xpack.notification.email')) {
$scope.ui.emailEnabled = true; $scope.ui.emailEnabled = true;
} }

View file

@ -14,38 +14,10 @@ import emailBody from './email.html';
import emailInfluencersBody from './email-influencers.html'; import emailInfluencersBody from './email-influencers.html';
import { watch } from './watch.js'; import { watch } from './watch.js';
import { uiModules } from 'ui/modules'; export function CreateWatchServiceProvider($http, $q, Private) {
const module = uiModules.get('apps/ml');
module.service('mlCreateWatchService', function ($http, $q, Private) {
const xpackInfo = Private(XPackInfoProvider); const xpackInfo = Private(XPackInfoProvider);
this.config = {};
this.STATUS = {
SAVE_FAILED: -1,
SAVING: 0,
SAVED: 1,
};
this.status = {
realtimeJob: null,
watch: null
};
this.reset = function () {
this.status.realtimeJob = null;
this.status.watch = null;
this.config.id = '';
this.config.includeEmail = false;
this.config.email = '';
this.config.interval = '20m';
this.config.watcherEditURL = '';
this.config.includeInfluencers = false;
this.config.threshold = { display: 'critical', val: 75 };
};
const compiledEmailBody = _.template(emailBody); const compiledEmailBody = _.template(emailBody);
const emailSection = { const emailSection = {
@ -67,8 +39,48 @@ module.service('mlCreateWatchService', function ($http, $q, Private) {
return Math.floor(Math.random() * (max - min + 1) + min); return Math.floor(Math.random() * (max - min + 1) + min);
} }
this.createNewWatch = function (jobId) { function saveWatch(watchModel) {
const deferred = $q.defer(); const basePath = chrome.addBasePath('/api/watcher');
const url = `${basePath}/watch/${watchModel.id}`;
return $http.put(url, watchModel.upstreamJSON)
.catch(e => {
throw e.data.message;
});
}
class CreateWatchService {
constructor() {
this.config = {};
this.STATUS = {
SAVE_FAILED: -1,
SAVING: 0,
SAVED: 1,
};
this.status = {
realtimeJob: null,
watch: null
};
}
reset() {
this.status.realtimeJob = null;
this.status.watch = null;
this.config.id = '';
this.config.includeEmail = false;
this.config.email = '';
this.config.interval = '20m';
this.config.watcherEditURL = '';
this.config.includeInfluencers = false;
this.config.threshold = { display: 'critical', val: 75 };
}
createNewWatch = function (jobId) {
return $q((resolve, reject) => {
this.status.watch = this.STATUS.SAVING; this.status.watch = this.STATUS.SAVING;
if (jobId !== undefined) { if (jobId !== undefined) {
const id = `ml-${jobId}`; const id = `ml-${jobId}`;
@ -95,7 +107,7 @@ module.service('mlCreateWatchService', function ($http, $q, Private) {
// set the trigger interval to be a random number between 60 and 120 seconds // set the trigger interval to be a random number between 60 and 120 seconds
// this is to avoid all watches firing at once if the server restarts // this is to avoid all watches firing at once if the server restarts
// and the watches synchronise // and the watches synchronize
const triggerInterval = randomNumber(60, 120); const triggerInterval = randomNumber(60, 120);
watch.trigger.schedule.interval = `${triggerInterval}s`; watch.trigger.schedule.interval = `${triggerInterval}s`;
@ -114,35 +126,25 @@ module.service('mlCreateWatchService', function ($http, $q, Private) {
this.status.watch = this.STATUS.SAVED; this.status.watch = this.STATUS.SAVED;
this.config.watcherEditURL = this.config.watcherEditURL =
`${chrome.getBasePath()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`; `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`;
deferred.resolve(); resolve();
}) })
.catch((resp) => { .catch((resp) => {
this.status.watch = this.STATUS.SAVE_FAILED; this.status.watch = this.STATUS.SAVE_FAILED;
deferred.reject(resp); reject(resp);
}); });
} }
} else { } else {
this.status.watch = this.STATUS.SAVE_FAILED; this.status.watch = this.STATUS.SAVE_FAILED;
deferred.reject(); reject();
} }
return deferred.promise;
};
function saveWatch(watchModel) {
const basePath = chrome.addBasePath('/api/watcher');
const url = `${basePath}/watch/${watchModel.id}`;
return $http.put(url, watchModel.upstreamJSON)
.catch(e => {
throw e.data.message;
}); });
} }
this.isWatcherEnabled = function () { isWatcherEnabled() {
return xpackInfo.get('features.watcher.isAvailable', false); return xpackInfo.get('features.watcher.isAvailable', false);
}; }
this.loadWatch = function (jobId) { loadWatch(jobId) {
const id = `ml-${jobId}`; const id = `ml-${jobId}`;
const basePath = chrome.addBasePath('/api/watcher'); const basePath = chrome.addBasePath('/api/watcher');
const url = `${basePath}/watch/${id}`; const url = `${basePath}/watch/${id}`;
@ -150,7 +152,8 @@ module.service('mlCreateWatchService', function ($http, $q, Private) {
.catch(e => { .catch(e => {
throw e.data.message; throw e.data.message;
}); });
}; }
}
return new CreateWatchService();
}); }

View file

@ -37,6 +37,9 @@ import {
createResultsUrl, createResultsUrl,
addNewJobToRecentlyAccessed, addNewJobToRecentlyAccessed,
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { MultiMetricJobServiceProvider } from './create_job_service';
import { FullTimeRangeSelectorServiceProvider } from 'plugins/ml/components/full_time_range_selector/full_time_range_selector_service';
import template from './create_job.html'; import template from './create_job.html';
uiRoutes uiRoutes
@ -61,10 +64,7 @@ module
$route, $route,
timefilter, timefilter,
Private, Private,
mlJobService,
mlMultiMetricJobService,
mlMessageBarService, mlMessageBarService,
mlFullTimeRangeSelectorService,
AppState) { AppState) {
timefilter.enableTimeRangeSelector(); timefilter.enableTimeRangeSelector();
@ -74,6 +74,9 @@ module
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider); const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
const calculateModelMemoryLimit = Private(CalculateModelMemoryLimitProvider); const calculateModelMemoryLimit = Private(CalculateModelMemoryLimitProvider);
const chartDataUtils = Private(ChartDataUtilsProvider); const chartDataUtils = Private(ChartDataUtilsProvider);
const mlJobService = Private(JobServiceProvider);
const mlMultiMetricJobService = Private(MultiMetricJobServiceProvider);
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
$scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed; $scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed;
const stateDefaults = { const stateDefaults = {

View file

@ -7,22 +7,25 @@
import _ from 'lodash'; import _ from 'lodash';
import angular from 'angular';
import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general'; import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general';
import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils'; import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { uiModules } from 'ui/modules'; export function MultiMetricJobServiceProvider(
const module = uiModules.get('apps/ml');
module.service('mlMultiMetricJobService', function (
$q, $q,
es, es,
timefilter, timefilter,
Private, Private) {
mlFieldFormatService,
mlJobService) {
const mlJobService = Private(JobServiceProvider);
const fieldFormatService = Private(FieldFormatServiceProvider);
class MultiMetricJobService {
constructor() {
this.chartData = { this.chartData = {
job: { job: {
swimlane: [], swimlane: [],
@ -39,8 +42,9 @@ module.service('mlMultiMetricJobService', function (
totalResults: 0 totalResults: 0
}; };
this.job = {}; this.job = {};
}
this.clearChartData = function () { clearChartData() {
this.chartData.job.swimlane = []; this.chartData.job.swimlane = [];
this.chartData.job.line = []; this.chartData.job.line = [];
this.chartData.job.bars = []; this.chartData.job.bars = [];
@ -49,12 +53,11 @@ module.service('mlMultiMetricJobService', function (
this.chartData.loadingDifference = 0; this.chartData.loadingDifference = 0;
this.chartData.eventRateHighestValue = 0; this.chartData.eventRateHighestValue = 0;
this.chartData.totalResults = 0; this.chartData.totalResults = 0;
this.job = {}; this.job = {};
}; }
this.getLineChartResults = function (formConfig, thisLoadTimestamp) { getLineChartResults(formConfig, thisLoadTimestamp) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const fieldIds = Object.keys(formConfig.fields).sort(); const fieldIds = Object.keys(formConfig.fields).sort();
@ -91,7 +94,7 @@ module.service('mlMultiMetricJobService', function (
if (fieldId !== EVENT_RATE_COUNT_FIELD) { if (fieldId !== EVENT_RATE_COUNT_FIELD) {
const field = formConfig.fields[fieldId]; const field = formConfig.fields[fieldId];
const aggType = field.agg.type.dslName; const aggType = field.agg.type.dslName;
this.chartData.detectors[fieldId].fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern( this.chartData.detectors[fieldId].fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
formConfig.indexPattern, formConfig.indexPattern,
fieldId, fieldId,
aggType); aggType);
@ -99,7 +102,7 @@ module.service('mlMultiMetricJobService', function (
}); });
} else { } else {
deferred.resolve(this.chartData); resolve(this.chartData);
} }
const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []);
@ -163,87 +166,16 @@ module.service('mlMultiMetricJobService', function (
}); });
}); });
deferred.resolve(this.chartData); resolve(this.chartData);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise;
};
function getSearchJsonFromConfig(formConfig) {
const interval = formConfig.chartInterval.getInterval().asMilliseconds() + 'ms';
// clone the query as we're modifying it
const query = _.cloneDeep(formConfig.combinedQuery);
const json = {
'index': formConfig.indexPattern.title,
'size': 0,
'body': {
'query': {},
'aggs': {
'times': {
'date_histogram': {
'field': formConfig.timeField,
'interval': interval,
'min_doc_count': 0,
'extended_bounds': {
'min': formConfig.start,
'max': formConfig.end,
}
}
}
}
}
};
query.bool.must.push({
'range': {
[formConfig.timeField]: {
'gte': formConfig.start,
'lte': formConfig.end,
'format': formConfig.format
}
}
});
// if the data is partitioned, add an additional search term
if (formConfig.firstSplitFieldName !== undefined) {
query.bool.must.push({
term: {
[formConfig.splitField.name]: formConfig.firstSplitFieldName
}
}); });
} }
json.body.query = query; getJobFromConfig(formConfig) {
if (Object.keys(formConfig.fields).length) {
json.body.aggs.times.aggs = {};
_.each(formConfig.fields, (field) => {
if (field.id !== EVENT_RATE_COUNT_FIELD) {
json.body.aggs.times.aggs[field.id] = {
[field.agg.type.dslName]: { field: field.name }
};
if (field.agg.type.dslName === 'percentiles') {
json.body.aggs.times.aggs[field.id][field.agg.type.dslName].percents = [ML_MEDIAN_PERCENTS];
}
}
});
}
return json;
}
function createJobForSaving(job) {
const newJob = angular.copy(job);
delete newJob.datafeed_config;
return newJob;
}
this.getJobFromConfig = function (formConfig) {
const job = mlJobService.getBlankJob(); const job = mlJobService.getBlankJob();
job.data_description.time_field = formConfig.timeField; job.data_description.time_field = formConfig.timeField;
@ -297,12 +229,10 @@ module.service('mlMultiMetricJobService', function (
delete job.data_description.format; delete job.data_description.format;
const indices = formConfig.indexPattern.title.split(',').map(i => i.trim()); const indices = formConfig.indexPattern.title.split(',').map(i => i.trim());
job.datafeed_config = { job.datafeed_config = {
query, query,
indices indices
}; };
job.job_id = formConfig.jobId; job.job_id = formConfig.jobId;
job.description = formConfig.description; job.description = formConfig.description;
job.groups = formConfig.jobGroups; job.groups = formConfig.jobGroups;
@ -312,10 +242,10 @@ module.service('mlMultiMetricJobService', function (
} }
return job; return job;
}; }
this.createJob = function (formConfig) { createJob(formConfig) {
const deferred = $q.defer(); return $q((resolve, reject) => {
this.job = this.getJobFromConfig(formConfig); this.job = this.getJobFromConfig(formConfig);
const job = createJobForSaving(this.job); const job = createJobForSaving(this.job);
@ -324,23 +254,92 @@ module.service('mlMultiMetricJobService', function (
mlJobService.saveNewJob(job) mlJobService.saveNewJob(job)
.then((resp) => { .then((resp) => {
if (resp.success) { if (resp.success) {
deferred.resolve(this.job); resolve(this.job);
} else { } else {
deferred.reject(resp); reject(resp);
} }
}); });
return deferred.promise; });
}; }
this.startDatafeed = function (formConfig) { startDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId); const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end); return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end);
}; }
this.stopDatafeed = function (formConfig) { stopDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId); const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.stopDatafeed(datafeedId, formConfig.jobId); return mlJobService.stopDatafeed(datafeedId, formConfig.jobId);
}
}
return new MultiMetricJobService();
}
function getSearchJsonFromConfig(formConfig) {
const interval = formConfig.chartInterval.getInterval().asMilliseconds() + 'ms';
// clone the query as we're modifying it
const query = _.cloneDeep(formConfig.combinedQuery);
const json = {
index: formConfig.indexPattern.title,
size: 0,
body: {
query: {},
aggs: {
times: {
date_histogram: {
field: formConfig.timeField,
interval: interval,
min_doc_count: 0,
extended_bounds: {
min: formConfig.start,
max: formConfig.end,
}
}
}
}
}
}; };
}); query.bool.must.push({
range: {
[formConfig.timeField]: {
gte: formConfig.start,
lte: formConfig.end,
format: formConfig.format
}
}
});
// if the data is partitioned, add an additional search term
if (formConfig.firstSplitFieldName !== undefined) {
query.bool.must.push({
term: {
[formConfig.splitField.name]: formConfig.firstSplitFieldName
}
});
}
json.body.query = query;
if (Object.keys(formConfig.fields).length) {
json.body.aggs.times.aggs = {};
_.each(formConfig.fields, (field) => {
if (field.id !== EVENT_RATE_COUNT_FIELD) {
json.body.aggs.times.aggs[field.id] = {
[field.agg.type.dslName]: { field: field.name }
};
if (field.agg.type.dslName === 'percentiles') {
json.body.aggs.times.aggs[field.id][field.agg.type.dslName].percents = [ML_MEDIAN_PERCENTS];
}
}
});
}
return json;
}

View file

@ -36,6 +36,9 @@ import {
createResultsUrl, createResultsUrl,
addNewJobToRecentlyAccessed, addNewJobToRecentlyAccessed,
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { PopulationJobServiceProvider } from './create_job_service';
import { FullTimeRangeSelectorServiceProvider } from 'plugins/ml/components/full_time_range_selector/full_time_range_selector_service';
import template from './create_job.html'; import template from './create_job.html';
uiRoutes uiRoutes
@ -62,10 +65,7 @@ module
$q, $q,
timefilter, timefilter,
Private, Private,
mlJobService,
mlPopulationJobService,
mlMessageBarService, mlMessageBarService,
mlFullTimeRangeSelectorService,
AppState) { AppState) {
timefilter.enableTimeRangeSelector(); timefilter.enableTimeRangeSelector();
@ -74,6 +74,9 @@ module
const MlTimeBuckets = Private(IntervalHelperProvider); const MlTimeBuckets = Private(IntervalHelperProvider);
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider); const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
const chartDataUtils = Private(ChartDataUtilsProvider); const chartDataUtils = Private(ChartDataUtilsProvider);
const mlJobService = Private(JobServiceProvider);
const mlPopulationJobService = Private(PopulationJobServiceProvider);
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
$scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed; $scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed;
const stateDefaults = { const stateDefaults = {

View file

@ -7,26 +7,29 @@
import _ from 'lodash'; import _ from 'lodash';
import angular from 'angular';
import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general'; import { EVENT_RATE_COUNT_FIELD } from 'plugins/ml/jobs/new_job/simple/components/constants/general';
import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils'; import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils';
import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets'; import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.service('mlPopulationJobService', function ( export function PopulationJobServiceProvider(
$q, $q,
es, es,
timefilter, timefilter,
Private, Private) {
mlFieldFormatService,
mlJobService) {
const mlJobService = Private(JobServiceProvider);
const TimeBuckets = Private(IntervalHelperProvider); const TimeBuckets = Private(IntervalHelperProvider);
const fieldFormatService = Private(FieldFormatServiceProvider);
const OVER_FIELD_EXAMPLES_COUNT = 40; const OVER_FIELD_EXAMPLES_COUNT = 40;
class PopulationJobService {
constructor() {
this.chartData = { this.chartData = {
job: { job: {
swimlane: [], swimlane: [],
@ -43,8 +46,9 @@ module.service('mlPopulationJobService', function (
totalResults: 0 totalResults: 0
}; };
this.job = {}; this.job = {};
}
this.clearChartData = function () { clearChartData() {
this.chartData.job.swimlane = []; this.chartData.job.swimlane = [];
this.chartData.job.line = []; this.chartData.job.line = [];
this.chartData.job.bars = []; this.chartData.job.bars = [];
@ -53,12 +57,11 @@ module.service('mlPopulationJobService', function (
this.chartData.loadingDifference = 0; this.chartData.loadingDifference = 0;
this.chartData.eventRateHighestValue = 0; this.chartData.eventRateHighestValue = 0;
this.chartData.totalResults = 0; this.chartData.totalResults = 0;
this.job = {}; this.job = {};
}; }
this.getLineChartResults = function (formConfig, thisLoadTimestamp) { getLineChartResults(formConfig, thisLoadTimestamp) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const fieldIds = formConfig.fields.map(f => f.id); const fieldIds = formConfig.fields.map(f => f.id);
@ -79,7 +82,7 @@ module.service('mlPopulationJobService', function (
}; };
}); });
const searchJson = getSearchJsonFromConfig(formConfig); const searchJson = getSearchJsonFromConfig(formConfig, timefilter, TimeBuckets);
es.search(searchJson) es.search(searchJson)
.then((resp) => { .then((resp) => {
@ -96,7 +99,7 @@ module.service('mlPopulationJobService', function (
if (fieldId !== EVENT_RATE_COUNT_FIELD) { if (fieldId !== EVENT_RATE_COUNT_FIELD) {
const field = formConfig.fields[i]; const field = formConfig.fields[i];
const aggType = field.agg.type.dslName; const aggType = field.agg.type.dslName;
this.chartData.detectors[i].fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern( this.chartData.detectors[i].fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
formConfig.indexPattern, formConfig.indexPattern,
fieldId, fieldId,
aggType); aggType);
@ -104,7 +107,7 @@ module.service('mlPopulationJobService', function (
}); });
} else { } else {
deferred.resolve(this.chartData); resolve(this.chartData);
} }
const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []);
@ -186,14 +189,122 @@ module.service('mlPopulationJobService', function (
}); });
}); });
deferred.resolve(this.chartData); resolve(this.chartData);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
});
});
}
getJobFromConfig(formConfig) {
const job = mlJobService.getBlankJob();
job.data_description.time_field = formConfig.timeField;
formConfig.fields.forEach(field => {
let func = field.agg.type.mlName;
if (formConfig.isSparseData) {
if (field.agg.type.dslName === 'count') {
func = func.replace(/count/, 'non_zero_count');
} else if(field.agg.type.dslName === 'sum') {
func = func.replace(/sum/, 'non_null_sum');
}
}
const dtr = {
function: func
};
dtr.detector_description = func;
if (field.id !== EVENT_RATE_COUNT_FIELD) {
dtr.field_name = field.name;
dtr.detector_description += `(${field.name})`;
}
if (field.splitField !== undefined) {
dtr.by_field_name = field.splitField.name;
dtr.detector_description += ` by ${dtr.by_field_name}`;
}
if (formConfig.overField !== undefined) {
dtr.over_field_name = formConfig.overField.name;
dtr.detector_description += ` over ${dtr.over_field_name}`;
}
// if (formConfig.splitField !== undefined) {
// dtr.partition_field_name = formConfig.splitField;
// }
job.analysis_config.detectors.push(dtr);
}); });
return deferred.promise; const influencerFields = formConfig.influencerFields.map(f => f.name);
if (influencerFields && influencerFields.length) {
job.analysis_config.influencers = influencerFields;
}
let query = {
match_all: {}
}; };
if (formConfig.query.query_string.query !== '*' || formConfig.filters.length) {
query = formConfig.combinedQuery;
}
job.analysis_config.bucket_span = formConfig.bucketSpan;
job.analysis_limits = {
model_memory_limit: formConfig.modelMemoryLimit
};
delete job.data_description.field_delimiter;
delete job.data_description.quote_character;
delete job.data_description.time_format;
delete job.data_description.format;
const indices = formConfig.indexPattern.title.split(',').map(i => i.trim());
job.datafeed_config = {
query,
indices,
};
job.job_id = formConfig.jobId;
job.description = formConfig.description;
job.groups = formConfig.jobGroups;
if (formConfig.useDedicatedIndex) {
job.results_index_name = job.job_id;
}
return job;
}
createJob(formConfig) {
return $q((resolve, reject) => {
this.job = this.getJobFromConfig(formConfig);
const job = createJobForSaving(this.job);
// DO THE SAVE
mlJobService.saveNewJob(job)
.then((resp) => {
if (resp.success) {
resolve(this.job);
} else {
reject(resp);
}
});
});
}
startDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end);
}
stopDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.stopDatafeed(datafeedId, formConfig.jobId);
}
}
function getSearchJsonFromConfig(formConfig) { function getSearchJsonFromConfig(formConfig) {
const bounds = timefilter.getActiveBounds(); const bounds = timefilter.getActiveBounds();
@ -207,19 +318,19 @@ module.service('mlPopulationJobService', function (
const query = _.cloneDeep(formConfig.combinedQuery); const query = _.cloneDeep(formConfig.combinedQuery);
const json = { const json = {
'index': formConfig.indexPattern.title, index: formConfig.indexPattern.title,
'size': 0, size: 0,
'body': { body: {
'query': {}, query: {},
'aggs': { aggs: {
'times': { times: {
'date_histogram': { date_histogram: {
'field': formConfig.timeField, field: formConfig.timeField,
'interval': interval, interval: interval,
'min_doc_count': 0, min_doc_count: 0,
'extended_bounds': { extended_bounds: {
'min': formConfig.start, min: formConfig.start,
'max': formConfig.end, max: formConfig.end,
} }
} }
} }
@ -228,11 +339,11 @@ module.service('mlPopulationJobService', function (
}; };
query.bool.must.push({ query.bool.must.push({
'range': { range: {
[formConfig.timeField]: { [formConfig.timeField]: {
'gte': formConfig.start, gte: formConfig.start,
'lte': formConfig.end, lte: formConfig.end,
'format': formConfig.format format: formConfig.format
} }
} }
}); });
@ -319,118 +430,5 @@ module.service('mlPopulationJobService', function (
return json; return json;
} }
function createJobForSaving(job) { return new PopulationJobService();
const newJob = angular.copy(job); }
delete newJob.datafeed_config;
return newJob;
}
this.getJobFromConfig = function (formConfig) {
const job = mlJobService.getBlankJob();
job.data_description.time_field = formConfig.timeField;
formConfig.fields.forEach(field => {
let func = field.agg.type.mlName;
if (formConfig.isSparseData) {
if (field.agg.type.dslName === 'count') {
func = func.replace(/count/, 'non_zero_count');
} else if(field.agg.type.dslName === 'sum') {
func = func.replace(/sum/, 'non_null_sum');
}
}
const dtr = {
function: func
};
dtr.detector_description = func;
if (field.id !== EVENT_RATE_COUNT_FIELD) {
dtr.field_name = field.name;
dtr.detector_description += `(${field.name})`;
}
if (field.splitField !== undefined) {
dtr.by_field_name = field.splitField.name;
dtr.detector_description += ` by ${dtr.by_field_name}`;
}
if (formConfig.overField !== undefined) {
dtr.over_field_name = formConfig.overField.name;
dtr.detector_description += ` over ${dtr.over_field_name}`;
}
// if (formConfig.splitField !== undefined) {
// dtr.partition_field_name = formConfig.splitField;
// }
job.analysis_config.detectors.push(dtr);
});
const influencerFields = formConfig.influencerFields.map(f => f.name);
if (influencerFields && influencerFields.length) {
job.analysis_config.influencers = influencerFields;
}
let query = {
match_all: {}
};
if (formConfig.query.query_string.query !== '*' || formConfig.filters.length) {
query = formConfig.combinedQuery;
}
job.analysis_config.bucket_span = formConfig.bucketSpan;
job.analysis_limits = {
model_memory_limit: formConfig.modelMemoryLimit
};
delete job.data_description.field_delimiter;
delete job.data_description.quote_character;
delete job.data_description.time_format;
delete job.data_description.format;
const indices = formConfig.indexPattern.title.split(',').map(i => i.trim());
job.datafeed_config = {
query,
indices
};
job.job_id = formConfig.jobId;
job.description = formConfig.description;
job.groups = formConfig.jobGroups;
if (formConfig.useDedicatedIndex) {
job.results_index_name = job.job_id;
}
return job;
};
this.createJob = function (formConfig) {
const deferred = $q.defer();
this.job = this.getJobFromConfig(formConfig);
const job = createJobForSaving(this.job);
// DO THE SAVE
mlJobService.saveNewJob(job)
.then((resp) => {
if (resp.success) {
deferred.resolve(this.job);
} else {
deferred.reject(resp);
}
});
return deferred.promise;
};
this.startDatafeed = function (formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end);
};
this.stopDatafeed = function (formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.stopDatafeed(datafeedId, formConfig.jobId);
};
});

View file

@ -19,6 +19,9 @@ import { checkLicenseExpired } from 'plugins/ml/license/check_license';
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import { getIndexPatternWithRoute, getSavedSearchWithRoute } from 'plugins/ml/util/index_utils'; import { getIndexPatternWithRoute, getSavedSearchWithRoute } from 'plugins/ml/util/index_utils';
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CreateRecognizerJobsServiceProvider } from './create_job_service';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './create_job.html'; import template from './create_job.html';
uiRoutes uiRoutes
@ -42,13 +45,12 @@ module
$window, $window,
$route, $route,
$q, $q,
ml,
timefilter, timefilter,
Private, Private,
mlCreateRecognizerJobsService,
mlJobService,
mlMessageBarService) { mlMessageBarService) {
const mlJobService = Private(JobServiceProvider);
const mlCreateRecognizerJobsService = Private(CreateRecognizerJobsServiceProvider);
timefilter.disableTimeRangeSelector(); timefilter.disableTimeRangeSelector();
timefilter.disableAutoRefreshSelector(); timefilter.disableAutoRefreshSelector();
$scope.tt = timefilter; $scope.tt = timefilter;
@ -145,7 +147,7 @@ module
function loadJobConfigs() { function loadJobConfigs() {
// load the job and datafeed configs as well as the kibana saved objects // load the job and datafeed configs as well as the kibana saved objects
// from the recognizer endpoint // from the recognizer endpoint
ml.getDataRecognizerModule({ moduleId }) $q.when(ml.getDataRecognizerModule({ moduleId }))
.then(resp => { .then(resp => {
// populate the jobs and datafeeds // populate the jobs and datafeeds
if (resp.jobs && resp.jobs.length) { if (resp.jobs && resp.jobs.length) {
@ -259,7 +261,7 @@ module
const tempQuery = (savedSearch.id === undefined) ? const tempQuery = (savedSearch.id === undefined) ?
undefined : combinedQuery; undefined : combinedQuery;
ml.setupDataRecognizerConfig({ moduleId, prefix, groups, query: tempQuery, indexPatternName }) $q.when(ml.setupDataRecognizerConfig({ moduleId, prefix, groups, query: tempQuery, indexPatternName }))
.then((resp) => { .then((resp) => {
if (resp.jobs) { if (resp.jobs) {
$scope.formConfig.jobs.forEach((job) => { $scope.formConfig.jobs.forEach((job) => {

View file

@ -6,44 +6,20 @@
import angular from 'angular';
import { getQueryFromSavedSearch } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; import { getQueryFromSavedSearch } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { ml } from 'plugins/ml/services/ml_api_service';
import { uiModules } from 'ui/modules'; export function CreateRecognizerJobsServiceProvider(Private, $q) {
const module = uiModules.get('apps/ml');
module.service('mlCreateRecognizerJobsService', function (
es,
Private,
$http,
$q,
chrome,
mlJobService) {
const mlJobService = Private(JobServiceProvider);
const savedObjectsClient = Private(SavedObjectsClientProvider); const savedObjectsClient = Private(SavedObjectsClientProvider);
class CreateRecognizerJobsService {
this.createJob = function (job, formConfig) { constructor() {}
return $q((resolve, reject) => {
const newJob = angular.copy(job.jobConfig);
const jobId = formConfig.jobLabel + job.id;
newJob.job_id = jobId;
newJob.groups = formConfig.jobGroups;
mlJobService.saveNewJob(newJob) createDatafeed(job, formConfig) {
.then((resp) => {
if (resp.success) {
resolve(resp);
} else {
reject(resp);
}
});
});
};
this.createDatafeed = function (job, formConfig) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const jobId = formConfig.jobLabel + job.id; const jobId = formConfig.jobLabel + job.id;
@ -55,77 +31,24 @@ module.service('mlCreateRecognizerJobsService', function (
reject(resp); reject(resp);
}); });
}); });
}; }
this.startDatafeed = function (datafeedId, jobId, start, end) { startDatafeed(datafeedId, jobId, start, end) {
return mlJobService.startDatafeed(datafeedId, jobId, start, end); return mlJobService.startDatafeed(datafeedId, jobId, start, end);
}; }
this.stopDatafeed = function (formConfig) { loadExistingSavedObjects(type) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.stopDatafeed(datafeedId, formConfig.jobId);
};
this.checkDatafeedStatus = function (formConfig) {
return mlJobService.updateSingleJobDatafeedState(formConfig.jobId);
};
this.loadExistingSavedObjects = function (type) {
return savedObjectsClient.find({ type, perPage: 1000 }); return savedObjectsClient.find({ type, perPage: 1000 });
}; }
this.createSavedObject = function (type, obj) { indexTimeRange(indexPattern, formConfig) {
return savedObjectsClient.create(type, obj);
};
this.createSavedObjectWithId = function (type, id, obj) {
const basePath = chrome.addBasePath('/api/saved_objects');
const url = `${basePath}/${type}/${id}`;
return $http.post(url, { attributes: obj })
.catch(e => {
throw e.data.message;
});
};
this.indexTimeRange = function (indexPattern, formConfig) {
return $q((resolve, reject) => {
const obj = { success: true, start: { epoch: 0, string: '' }, end: { epoch: 0, string: '' } };
const query = getQueryFromSavedSearch(formConfig); const query = getQueryFromSavedSearch(formConfig);
return ml.getTimeFieldRange({
es.search({
index: indexPattern.title, index: indexPattern.title,
size: 0, timeFieldName: indexPattern.timeFieldName,
body: { query
query,
aggs: {
earliest: {
min: {
field: indexPattern.timeFieldName
}
},
latest: {
max: {
field: indexPattern.timeFieldName
}
}
}
}
})
.then((resp) => {
if (resp.aggregations && resp.aggregations.earliest && resp.aggregations.latest) {
obj.start.epoch = resp.aggregations.earliest.value;
obj.start.string = resp.aggregations.earliest.value_as_string;
obj.end.epoch = resp.aggregations.latest.value;
obj.end.string = resp.aggregations.latest.value_as_string;
}
resolve(obj);
})
.catch((resp) => {
reject(resp);
}); });
}); }
}; }
return new CreateRecognizerJobsService();
}); }

View file

@ -36,6 +36,9 @@ import {
createResultsUrl, createResultsUrl,
addNewJobToRecentlyAccessed, addNewJobToRecentlyAccessed,
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils'; moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { SingleMetricJobServiceProvider } from './create_job_service';
import { FullTimeRangeSelectorServiceProvider } from 'plugins/ml/components/full_time_range_selector/full_time_range_selector_service';
import template from './create_job.html'; import template from './create_job.html';
@ -63,10 +66,7 @@ module
$q, $q,
timefilter, timefilter,
Private, Private,
mlJobService,
mlSingleMetricJobService,
mlMessageBarService, mlMessageBarService,
mlFullTimeRangeSelectorService,
AppState) { AppState) {
timefilter.enableTimeRangeSelector(); timefilter.enableTimeRangeSelector();
@ -74,6 +74,9 @@ module
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
const MlTimeBuckets = Private(IntervalHelperProvider); const MlTimeBuckets = Private(IntervalHelperProvider);
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider); const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
const mlJobService = Private(JobServiceProvider);
const mlSingleMetricJobService = Private(SingleMetricJobServiceProvider);
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
const stateDefaults = { const stateDefaults = {
mlJobSettings: {} mlJobSettings: {}

View file

@ -7,26 +7,28 @@
import _ from 'lodash'; import _ from 'lodash';
import angular from 'angular';
import 'ui/timefilter';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils'; import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils';
import { calculateTextWidth } from 'plugins/ml/util/string_utils'; import { calculateTextWidth } from 'plugins/ml/util/string_utils';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
import { uiModules } from 'ui/modules'; export function SingleMetricJobServiceProvider(
const module = uiModules.get('apps/ml');
module.service('mlSingleMetricJobService', function (
$q, $q,
es, es,
timefilter, Private) {
Private,
mlFieldFormatService,
mlJobService,
mlResultsService) {
const mlJobService = Private(JobServiceProvider);
const mlResultsService = Private(ResultsServiceProvider);
const fieldFormatService = Private(FieldFormatServiceProvider);
class SingleMetricJobService {
constructor() {
this.chartData = { this.chartData = {
line: [], line: [],
model: [], model: [],
@ -38,9 +40,10 @@ module.service('mlSingleMetricJobService', function (
totalResults: 0 totalResults: 0
}; };
this.job = {}; this.job = {};
}
this.getLineChartResults = function (formConfig) { getLineChartResults(formConfig) {
const deferred = $q.defer(); return $q((resolve, reject) => {
this.chartData.line = []; this.chartData.line = [];
this.chartData.model = []; this.chartData.model = [];
@ -53,7 +56,7 @@ module.service('mlSingleMetricJobService', function (
const aggType = formConfig.agg.type.dslName; const aggType = formConfig.agg.type.dslName;
if (formConfig.field && formConfig.field.id) { if (formConfig.field && formConfig.field.id) {
this.chartData.fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern( this.chartData.fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
formConfig.indexPattern, formConfig.indexPattern,
formConfig.field.id, formConfig.field.id,
aggType); aggType);
@ -109,107 +112,16 @@ module.service('mlSingleMetricJobService', function (
this.chartData.chartTicksMargin.width = calculateTextWidth(this.chartData.highestValue, true) + 10; this.chartData.chartTicksMargin.width = calculateTextWidth(this.chartData.highestValue, true) + 10;
} }
deferred.resolve(this.chartData); resolve(this.chartData);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise;
};
function processLineChartResults(data, scale = 1) {
const lineData = [];
_.each(data, (dataForTime, t) => {
const time = +t;
const date = new Date(time);
lineData.push({
date: date,
time: time,
lower: (dataForTime.modelLower * scale),
value: dataForTime.actual,
upper: (dataForTime.modelUpper * scale)
}); });
});
return _.sortBy(lineData, 'time');
} }
function processSwimlaneResults(bucketScoreData, init) { getJobFromConfig(formConfig) {
// create a dataset in format used by the model plot chart.
// create empty swimlane dataset
// i.e. array of Objects with keys date (JavaScript date), value, lower and upper.
const swimlaneData = [];
_.each(bucketScoreData, (value, t) => {
const time = +t;
const date = new Date(time);
value = init ? 0 : value;
swimlaneData.push({
date,
time,
value,
color: ''
});
});
return swimlaneData;
}
function getSearchJsonFromConfig(formConfig) {
const interval = formConfig.chartInterval.getInterval().asMilliseconds() + 'ms';
// clone the query as we're modifying it
const query = _.cloneDeep(formConfig.combinedQuery);
const json = {
'index': formConfig.indexPattern.title,
'size': 0,
'body': {
'query': {},
'aggs': {
'times': {
'date_histogram': {
'field': formConfig.timeField,
'interval': interval,
'min_doc_count': 0,
'extended_bounds': {
'min': formConfig.start,
'max': formConfig.end,
}
}
}
}
}
};
query.bool.must.push({
'range': {
[formConfig.timeField]: {
'gte': formConfig.start,
'lte': formConfig.end,
'format': formConfig.format
}
}
});
json.body.query = query;
if (formConfig.field !== null) {
json.body.aggs.times.aggs = {
'field_value': {
[formConfig.agg.type.dslName]: { field: formConfig.field.name }
}
};
}
return json;
}
function createJobForSaving(job) {
const newJob = angular.copy(job);
delete newJob.datafeed_config;
return newJob;
}
this.getJobFromConfig = function (formConfig) {
const job = mlJobService.getBlankJob(); const job = mlJobService.getBlankJob();
job.data_description.time_field = formConfig.timeField; job.data_description.time_field = formConfig.timeField;
@ -250,7 +162,6 @@ module.service('mlSingleMetricJobService', function (
const bucketSpanSeconds = parseInterval(formConfig.bucketSpan).asSeconds(); const bucketSpanSeconds = parseInterval(formConfig.bucketSpan).asSeconds();
const indices = formConfig.indexPattern.title.split(',').map(i => i.trim()); const indices = formConfig.indexPattern.title.split(',').map(i => i.trim());
job.datafeed_config = { job.datafeed_config = {
query, query,
indices, indices,
@ -356,13 +267,11 @@ module.service('mlSingleMetricJobService', function (
break; break;
} }
console.log('auto created job: ', job);
return job; return job;
}; }
this.createJob = function (formConfig) { createJob(formConfig) {
const deferred = $q.defer(); return $q((resolve, reject) => {
this.job = this.getJobFromConfig(formConfig); this.job = this.getJobFromConfig(formConfig);
const job = createJobForSaving(this.job); const job = createJobForSaving(this.job);
@ -371,31 +280,31 @@ module.service('mlSingleMetricJobService', function (
mlJobService.saveNewJob(job) mlJobService.saveNewJob(job)
.then((resp) => { .then((resp) => {
if (resp.success) { if (resp.success) {
deferred.resolve(this.job); resolve(this.job);
} else { } else {
deferred.reject(resp); reject(resp);
} }
}); });
return deferred.promise; });
}; }
this.startDatafeed = function (formConfig) { startDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId); const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end); return mlJobService.startDatafeed(datafeedId, formConfig.jobId, formConfig.start, formConfig.end);
}; }
this.stopDatafeed = function (formConfig) { stopDatafeed(formConfig) {
const datafeedId = mlJobService.getDatafeedId(formConfig.jobId); const datafeedId = mlJobService.getDatafeedId(formConfig.jobId);
return mlJobService.stopDatafeed(datafeedId, formConfig.jobId); return mlJobService.stopDatafeed(datafeedId, formConfig.jobId);
}; }
this.checkDatafeedState = function (formConfig) { checkDatafeedState(formConfig) {
return mlJobService.updateSingleJobDatafeedState(formConfig.jobId); return mlJobService.updateSingleJobDatafeedState(formConfig.jobId);
}; }
this.loadModelData = function (formConfig) { loadModelData(formConfig) {
const deferred = $q.defer(); return $q((resolve, reject) => {
let start = formConfig.start; let start = formConfig.start;
@ -443,17 +352,17 @@ module.service('mlSingleMetricJobService', function (
const pcnt = ((time - formConfig.start + formConfig.resultsIntervalSeconds) / (formConfig.end - formConfig.start) * 100); const pcnt = ((time - formConfig.start + formConfig.resultsIntervalSeconds) / (formConfig.end - formConfig.start) * 100);
this.chartData.percentComplete = Math.round(pcnt); this.chartData.percentComplete = Math.round(pcnt);
deferred.resolve(this.chartData); resolve(this.chartData);
}) })
.catch(() => { .catch(() => {
deferred.reject(this.chartData); reject(this.chartData);
}); });
return deferred.promise; });
}; }
this.loadSwimlaneData = function (formConfig) { loadSwimlaneData(formConfig) {
const deferred = $q.defer(); return $q((resolve) => {
mlResultsService.getScoresByBucket( mlResultsService.getScoresByBucket(
[formConfig.jobId], [formConfig.jobId],
@ -466,12 +375,100 @@ module.service('mlSingleMetricJobService', function (
const jobResults = data.results[formConfig.jobId]; const jobResults = data.results[formConfig.jobId];
this.chartData.swimlane = processSwimlaneResults(jobResults); this.chartData.swimlane = processSwimlaneResults(jobResults);
this.chartData.swimlaneInterval = formConfig.resultsIntervalSeconds * 1000; this.chartData.swimlaneInterval = formConfig.resultsIntervalSeconds * 1000;
deferred.resolve(this.chartData); resolve(this.chartData);
}) })
.catch(() => { .catch(() => {
deferred.resolve(this.chartData); resolve(this.chartData);
}); });
return deferred.promise; });
}
}
return new SingleMetricJobService();
}
function processLineChartResults(data, scale = 1) {
const lineData = [];
_.each(data, (dataForTime, t) => {
const time = +t;
const date = new Date(time);
lineData.push({
date: date,
time: time,
lower: (dataForTime.modelLower * scale),
value: dataForTime.actual,
upper: (dataForTime.modelUpper * scale)
});
});
return _.sortBy(lineData, 'time');
}
function processSwimlaneResults(bucketScoreData, init) {
// create a dataset in format used by the model plot chart.
// create empty swimlane dataset
// i.e. array of Objects with keys date (JavaScript date), value, lower and upper.
const swimlaneData = [];
_.each(bucketScoreData, (value, t) => {
const time = +t;
const date = new Date(time);
value = init ? 0 : value;
swimlaneData.push({
date,
time,
value,
color: ''
});
});
return swimlaneData;
}
function getSearchJsonFromConfig(formConfig) {
const interval = formConfig.chartInterval.getInterval().asMilliseconds() + 'ms';
// clone the query as we're modifying it
const query = _.cloneDeep(formConfig.combinedQuery);
const json = {
index: formConfig.indexPattern.title,
size: 0,
body: {
query: {},
aggs: {
times: {
date_histogram: {
field: formConfig.timeField,
interval: interval,
min_doc_count: 0,
extended_bounds: {
min: formConfig.start,
max: formConfig.end,
}
}
}
}
}
}; };
});
query.bool.must.push({
range: {
[formConfig.timeField]: {
gte: formConfig.start,
lte: formConfig.end,
format: formConfig.format
}
}
});
json.body.query = query;
if (formConfig.field !== null) {
json.body.aggs.times.aggs = {
field_value: {
[formConfig.agg.type.dslName]: { field: formConfig.field.name }
}
};
}
return json;
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { ml } from 'plugins/ml/services/ml_api_service';
let defaults = { let defaults = {
anomaly_detectors: {}, anomaly_detectors: {},
@ -12,7 +12,7 @@ let defaults = {
}; };
let limits = {}; let limits = {};
export function loadNewJobDefaults(ml) { export function loadNewJobDefaults() {
return new Promise((resolve) => { return new Promise((resolve) => {
ml.mlInfo() ml.mlInfo()
.then((resp) => { .then((resp) => {

View file

@ -10,6 +10,7 @@ import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { migrateFilter } from 'ui/courier/data_source/_migrate_filter.js'; import { migrateFilter } from 'ui/courier/data_source/_migrate_filter.js';
import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed'; import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
export function getQueryFromSavedSearch(formConfig) { export function getQueryFromSavedSearch(formConfig) {
const must = []; const must = [];
@ -103,12 +104,19 @@ export function createResultsUrl(jobIds, start, end, resultsPage) {
return path; return path;
} }
export function createJobForSaving(job) {
const newJob = _.cloneDeep(job);
delete newJob.datafeed_config;
return newJob;
}
export function addNewJobToRecentlyAccessed(jobId, resultsUrl) { export function addNewJobToRecentlyAccessed(jobId, resultsUrl) {
const urlParts = resultsUrl.match(/ml#\/(.+?)(\?.+)/); const urlParts = resultsUrl.match(/ml#\/(.+?)(\?.+)/);
addItemToRecentlyAccessed(urlParts[1], jobId, urlParts[2]); addItemToRecentlyAccessed(urlParts[1], jobId, urlParts[2]);
} }
export function moveToAdvancedJobCreationProvider(mlJobService, $location) { export function moveToAdvancedJobCreationProvider(Private, $location) {
const mlJobService = Private(JobServiceProvider);
return function moveToAdvancedJobCreation(job) { return function moveToAdvancedJobCreation(job) {
mlJobService.currentJob = job; mlJobService.currentJob = job;
$location.path('jobs/new_job/advanced'); $location.path('jobs/new_job/advanced');

View file

@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { ml } from 'plugins/ml/services/ml_api_service';
let mlNodeCount = 0; let mlNodeCount = 0;
let userHasPermissionToViewMlNodeCount = false; let userHasPermissionToViewMlNodeCount = false;
export function checkMlNodesAvailable(ml, kbnUrl) { export function checkMlNodesAvailable(kbnUrl) {
getMlNodeCount(ml).then((nodes) => { getMlNodeCount().then((nodes) => {
if (nodes.count !== undefined && nodes.count > 0) { if (nodes.count !== undefined && nodes.count > 0) {
Promise.resolve(); Promise.resolve();
} else { } else {
@ -20,7 +20,7 @@ export function checkMlNodesAvailable(ml, kbnUrl) {
}); });
} }
export function getMlNodeCount(ml) { export function getMlNodeCount() {
return new Promise((resolve) => { return new Promise((resolve) => {
ml.mlNodeCount() ml.mlNodeCount()
.then((nodes) => { .then((nodes) => {

View file

@ -5,8 +5,9 @@
*/ */
import { ml } from 'plugins/ml/services/ml_api_service';
export function privilegesProvider(Promise, ml) { export function privilegesProvider() {
function getPrivileges() { function getPrivileges() {
const privileges = { const privileges = {

View file

@ -8,18 +8,25 @@
import _ from 'lodash'; import _ from 'lodash';
import { uiModules } from 'ui/modules'; import { ml } from 'plugins/ml/services/ml_api_service';
const module = uiModules.get('apps/ml'); import { JobServiceProvider } from 'plugins/ml/services/job_service';
module.service('mlCalendarService', function ($q, ml, mlJobService, mlMessageBarService) { let calendarService = undefined;
export function CalendarServiceProvider($q, Private, mlMessageBarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
const mlJobService = Private(JobServiceProvider);
class CalendarService {
constructor() {
this.calendars = []; this.calendars = [];
// list of calendar ids per job id // list of calendar ids per job id
this.jobCalendars = {}; this.jobCalendars = {};
// list of calendar ids per group id // list of calendar ids per group id
this.groupCalendars = {}; this.groupCalendars = {};
}
this.loadCalendars = function (jobs) { loadCalendars(jobs) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
let calendars = []; let calendars = [];
jobs.forEach((j) => { jobs.forEach((j) => {
@ -77,10 +84,16 @@ module.service('mlCalendarService', function ($q, ml, mlJobService, mlMessageBar
reject({ calendars, err }); reject({ calendars, err });
}); });
}); });
}; }
// get the list of calendar groups // get the list of calendar groups
this.getCalendarGroups = function () { getCalendarGroups() {
return Object.keys(this.groupCalendars).map(gId => ({ id: gId })); return Object.keys(this.groupCalendars).map(id => ({ id }));
}; }
}); }
if (calendarService === undefined) {
calendarService = new CalendarService();
}
return calendarService;
}

View file

@ -11,32 +11,31 @@ import _ from 'lodash';
import 'ui/courier'; import 'ui/courier';
import { mlFunctionToESAggregation } from 'plugins/ml/../common/util/job_utils'; import { mlFunctionToESAggregation } from 'plugins/ml/../common/util/job_utils';
import { getIndexPatternProvider } from 'plugins/ml/util/index_utils'; import { getIndexPatternProvider } from 'plugins/ml/util/index_utils';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
// Service for accessing FieldFormat objects configured for a Kibana index pattern // Service for accessing FieldFormat objects configured for a Kibana index pattern
// for use in formatting the actual and typical values from anomalies. // for use in formatting the actual and typical values from anomalies.
module.service('mlFieldFormatService', function ( export function FieldFormatServiceProvider(
$q, $q,
courier, courier,
Private, Private) {
mlJobService) { const mlJobService = Private(JobServiceProvider);
const indexPatternIdsByJob = {};
const formatsByJob = {};
const getIndexPattern = Private(getIndexPatternProvider); const getIndexPattern = Private(getIndexPatternProvider);
class FieldFormatService {
constructor() {
this.indexPatternIdsByJob = {};
this.formatsByJob = {};
}
// Populate the service with the FieldFormats for the list of jobs with the // Populate the service with the FieldFormats for the list of jobs with the
// specified IDs. List of Kibana index patterns is passed, with a title // specified IDs. List of Kibana index patterns is passed, with a title
// attribute set in each pattern which will be compared to the index pattern // attribute set in each pattern which will be compared to the index pattern
// configured in the datafeed of each job. // configured in the datafeed of each job.
// Builds a map of Kibana FieldFormats (ui/field_formats/field_format.js) // Builds a map of Kibana FieldFormats (ui/field_formats/field_format.js)
// against detector index by job ID. // against detector index by job ID.
this.populateFormats = function (jobIds, indexPatterns) { populateFormats(jobIds, indexPatterns) {
const deferred = $q.defer(); return $q((resolve, reject) => {
// Populate a map of index pattern IDs against job ID, by finding the ID of the index // Populate a map of index pattern IDs against job ID, by finding the ID of the index
// pattern with a title attribute which matches the index configured in the datafeed. // pattern with a title attribute which matches the index configured in the datafeed.
// If a Kibana index pattern has not been created // If a Kibana index pattern has not been created
@ -52,39 +51,38 @@ module.service('mlFieldFormatService', function (
// Check if index pattern has been configured to match the index in datafeed. // Check if index pattern has been configured to match the index in datafeed.
if (indexPattern !== undefined) { if (indexPattern !== undefined) {
indexPatternIdsByJob[jobId] = indexPattern.id; this.indexPatternIdsByJob[jobId] = indexPattern.id;
} }
}); });
const promises = jobIds.map(jobId => $q.all([ const promises = jobIds.map(jobId => $q.all([
getFormatsForJob(jobId) this.getFormatsForJob(jobId)
])); ]));
$q.all(promises).then((fmtsByJobByDetector) => { $q.all(promises).then((fmtsByJobByDetector) => {
_.each(fmtsByJobByDetector, (formatsByDetector, index) => { _.each(fmtsByJobByDetector, (formatsByDetector, index) => {
formatsByJob[jobIds[index]] = formatsByDetector[0]; this.formatsByJob[jobIds[index]] = formatsByDetector[0];
}); });
deferred.resolve(formatsByJob); resolve(this.formatsByJob);
}).catch(err => { }).catch(err => {
console.log('mlFieldFormatService error populating formats:', err); console.log('fieldFormatService error populating formats:', err);
deferred.reject({ formats: {}, err }); reject({ formats: {}, err });
}); });
return deferred.promise; });
}
};
// Return the FieldFormat to use for formatting values from // Return the FieldFormat to use for formatting values from
// the detector from the job with the specified ID. // the detector from the job with the specified ID.
this.getFieldFormat = function (jobId, detectorIndex) { getFieldFormat(jobId, detectorIndex) {
return _.get(formatsByJob, [jobId, detectorIndex]); return _.get(this.formatsByJob, [jobId, detectorIndex]);
}; }
// Utility for returning the FieldFormat from a full populated Kibana index pattern object // Utility for returning the FieldFormat from a full populated Kibana index pattern object
// containing the list of fields by name with their formats. // containing the list of fields by name with their formats.
this.getFieldFormatFromIndexPattern = function (fullIndexPattern, fieldName, esAggName) { getFieldFormatFromIndexPattern(fullIndexPattern, fieldName, esAggName) {
// Don't use the field formatter for distinct count detectors as // Don't use the field formatter for distinct count detectors as
// e.g. distinct_count(clientip) should be formatted as a count, not as an IP address. // e.g. distinct_count(clientip) should be formatted as a count, not as an IP address.
let fieldFormat = undefined; let fieldFormat = undefined;
@ -94,16 +92,16 @@ module.service('mlFieldFormatService', function (
} }
return fieldFormat; return fieldFormat;
}; }
function getFormatsForJob(jobId) { getFormatsForJob(jobId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const jobObj = mlJobService.getJob(jobId); const jobObj = mlJobService.getJob(jobId);
const detectors = jobObj.analysis_config.detectors || []; const detectors = jobObj.analysis_config.detectors || [];
const formatsByDetector = {}; const formatsByDetector = {};
const indexPatternId = indexPatternIdsByJob[jobId]; const indexPatternId = this.indexPatternIdsByJob[jobId];
if (indexPatternId !== undefined) { if (indexPatternId !== undefined) {
// Load the full index pattern configuration to obtain the formats of each field. // Load the full index pattern configuration to obtain the formats of each field.
getIndexPattern(indexPatternId) getIndexPattern(indexPatternId)
@ -119,16 +117,16 @@ module.service('mlFieldFormatService', function (
} }
}); });
deferred.resolve(formatsByDetector); resolve(formatsByDetector);
}).catch(err => { }).catch(err => {
deferred.reject(err); reject(err);
}); });
return deferred.promise;
} else { } else {
deferred.resolve(formatsByDetector); resolve(formatsByDetector);
}
});
}
} }
} return new FieldFormatService();
}
});

View file

@ -1,57 +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.
*/
// Service for carrying out queries to obtain data
// specific to fields in Elasticsearch indices.
export function FieldsServiceProvider(es, ml) {
// Obtains the cardinality of one or more fields.
// Returns an Object whose keys are the names of the fields,
// with values equal to the cardinality of the field.
function getCardinalityOfFields(
index,
types,
fieldNames,
query,
timeFieldName,
earliestMs,
latestMs) {
return ml.getCardinalityOfFields({
index,
types,
fieldNames,
query,
timeFieldName,
earliestMs,
latestMs
});
}
// Returns the range of the specified time field.
// Returns an Object containing start and end properties,
// holding the value as an epoch (ms since the Unix epoch)
// and as a formatted string.
function getTimeFieldRange(
index,
timeFieldName,
query) {
return ml.getTimeFieldRange({
index,
timeFieldName,
query
});
}
return {
getCardinalityOfFields,
getTimeFieldRange
};
}

View file

@ -11,24 +11,22 @@
import _ from 'lodash'; import _ from 'lodash';
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
import { ml } from 'plugins/ml/services/ml_api_service';
import { uiModules } from 'ui/modules'; export function ForecastServiceProvider(es, $q) {
const module = uiModules.get('apps/ml');
module.service('mlForecastService', function ($q, es, ml) {
// Gets a basic summary of the most recently run forecasts for the specified // Gets a basic summary of the most recently run forecasts for the specified
// job, with results at or later than the supplied timestamp. // job, with results at or later than the supplied timestamp.
// Extra query object can be supplied, or pass null if no additional query. // Extra query object can be supplied, or pass null if no additional query.
// Returned response contains a forecasts property, which is an array of objects // Returned response contains a forecasts property, which is an array of objects
// containing id, earliest and latest keys. // containing id, earliest and latest keys.
this.getForecastsSummary = function ( function getForecastsSummary(
job, job,
query, query,
earliestMs, earliestMs,
maxResults maxResults
) { ) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
forecasts: [] forecasts: []
@ -74,27 +72,24 @@ module.service('mlForecastService', function ($q, es, ml) {
}) })
.then((resp) => { .then((resp) => {
if (resp.hits.total !== 0) { if (resp.hits.total !== 0) {
_.each(resp.hits.hits, (hit) => { obj.forecasts = resp.hits.hits.map(hit => hit._source);
obj.forecasts.push(hit._source);
});
} }
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
});
return deferred.promise; }
};
// Obtains the earliest and latest timestamps for the forecast data from // Obtains the earliest and latest timestamps for the forecast data from
// the forecast with the specified ID. // the forecast with the specified ID.
// Returned response contains earliest and latest properties which are the // Returned response contains earliest and latest properties which are the
// timestamps of the first and last model_forecast results. // timestamps of the first and last model_forecast results.
this.getForecastDateRange = function (job, forecastId) { function getForecastDateRange(job, forecastId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
earliest: null, earliest: null,
@ -146,20 +141,20 @@ module.service('mlForecastService', function ($q, es, ml) {
obj.earliest = _.get(resp, 'aggregations.earliest.value', null); obj.earliest = _.get(resp, 'aggregations.earliest.value', null);
obj.latest = _.get(resp, 'aggregations.latest.value', null); obj.latest = _.get(resp, 'aggregations.latest.value', null);
if (obj.earliest === null || obj.latest === null) { if (obj.earliest === null || obj.latest === null) {
deferred.reject(resp); reject(resp);
} else { } else {
deferred.resolve(obj); resolve(obj);
} }
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
// Obtains the requested forecast model data for the forecast with the specified ID. // Obtains the requested forecast model data for the forecast with the specified ID.
this.getForecastData = function ( function getForecastData(
job, job,
detectorIndex, detectorIndex,
forecastId, forecastId,
@ -198,7 +193,7 @@ module.service('mlForecastService', function ($q, es, ml) {
} }
} }
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
results: {} results: {}
@ -302,38 +297,38 @@ module.service('mlForecastService', function ($q, es, ml) {
}; };
}); });
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
// Runs a forecast // Runs a forecast
this.runForecast = function (jobId, duration) { function runForecast(jobId, duration) {
console.log('ML forecast service run forecast with duration:', duration); console.log('ML forecast service run forecast with duration:', duration);
const deferred = $q.defer(); return $q((resolve, reject) => {
ml.forecast({ ml.forecast({
jobId, jobId,
duration duration
}) })
.then((resp) => { .then((resp) => {
deferred.resolve(resp); resolve(resp);
}).catch((err) => { }).catch((err) => {
deferred.reject(err); reject(err);
}); });
return deferred.promise; });
}; }
// Gets stats for a forecast that has been run on the specified job. // Gets stats for a forecast that has been run on the specified job.
// Returned response contains a stats property, including // Returned response contains a stats property, including
// forecast_progress (a value from 0 to 1), // forecast_progress (a value from 0 to 1),
// and forecast_status ('finished' when complete) properties. // and forecast_status ('finished' when complete) properties.
this.getForecastRequestStats = function (job, forecastId) { function getForecastRequestStats(job, forecastId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
stats: {} stats: {}
@ -369,13 +364,21 @@ module.service('mlForecastService', function ($q, es, ml) {
if (resp.hits.total !== 0) { if (resp.hits.total !== 0) {
obj.stats = _.first(resp.hits.hits)._source; obj.stats = _.first(resp.hits.hits)._source;
} }
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}
return {
getForecastsSummary,
getForecastDateRange,
getForecastData,
runForecast,
getForecastRequestStats
}; };
}); }

View file

@ -8,43 +8,39 @@
// service for interacting with the server // service for interacting with the server
import { uiModules } from 'ui/modules'; import chrome from 'ui/chrome';
const module = uiModules.get('apps/ml'); import 'isomorphic-fetch';
import { addSystemApiHeader } from 'ui/system_api'; import { addSystemApiHeader } from 'ui/system_api';
module.service('prlHttpService', function ($http, $q) { export function http(options) {
return new Promise((resolve, reject) => {
// request function returns a promise
// once resolved, just the data response is returned
this.request = function (options) {
if(options && options.url) { if(options && options.url) {
let url = ''; let url = '';
url = url + (options.url || ''); url = url + (options.url || '');
const headers = addSystemApiHeader({}); const headers = addSystemApiHeader({
const allHeaders = (options.headers === undefined) ? 'Content-Type': 'application/json',
headers : 'kbn-version': chrome.getXsrfToken(),
Object.assign(options.headers, headers); ...options.headers
const deferred = $q.defer();
$http({
url: url,
method: (options.method || 'GET'),
headers: (allHeaders),
params: (options.params || {}),
data: (options.data || null)
})
.then(function successCallback(response) {
deferred.resolve(response.data);
}, function errorCallback(response) {
deferred.reject(response.data);
}); });
return deferred.promise; const allHeaders = (options.headers === undefined) ? headers : { ...options.headers, ...headers };
const body = (options.data === undefined) ? null : JSON.stringify(options.data);
fetch(url, {
method: (options.method || 'GET'),
headers: (allHeaders),
credentials: 'same-origin',
body,
})
.then((resp) => {
resolve(resp.json());
})
.catch((resp) => {
reject(resp);
});
} else {
reject();
} }
}; });
}
});

View file

@ -0,0 +1,170 @@
/*
* 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.
*/
// Service for carrying out Elasticsearch queries to obtain data for the
// Ml Results dashboards.
import { ML_NOTIFICATION_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
export function JobMessagesServiceProvider(es, $q) {
// search for audit messages, jobId is optional.
// without it, all jobs will be listed.
// fromRange should be a string formatted in ES time units. e.g. 12h, 1d, 7d
function getJobAuditMessages(fromRange, jobId) {
return $q((resolve, reject) => {
let jobFilter = {};
// if no jobId specified, load all of the messages
if (jobId !== undefined) {
jobFilter = {
bool: {
should: [
{
term: {
job_id: '' // catch system messages
}
},
{
term: {
job_id: jobId // messages for specified jobId
}
}
]
}
};
}
let timeFilter = {};
if (fromRange !== undefined && fromRange !== '') {
timeFilter = {
range: {
timestamp: {
gte: `now-${fromRange}`,
lte: 'now'
}
}
};
}
es.search({
index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: 1000,
body:
{
sort: [
{ timestamp: { order: 'asc' } },
{ job_id: { order: 'asc' } }
],
query: {
bool: {
filter: [
{
bool: {
must_not: {
term: {
level: 'activity'
}
}
}
},
jobFilter,
timeFilter
]
}
}
}
})
.then((resp) => {
let messages = [];
if (resp.hits.total !== 0) {
messages = resp.hits.hits.map(hit => hit._source);
}
resolve({ messages });
})
.catch((resp) => {
reject(resp);
});
});
}
// search highest, most recent audit messages for all jobs for the last 24hrs.
function getAuditMessagesSummary() {
return $q((resolve, reject) => {
es.search({
index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: 0,
body: {
query: {
bool: {
filter: {
range: {
timestamp: {
gte: 'now-1d'
}
}
}
}
},
aggs: {
levelsPerJob: {
terms: {
field: 'job_id',
},
aggs: {
levels: {
terms: {
field: 'level',
},
aggs: {
latestMessage: {
terms: {
field: 'message.raw',
size: 1,
order: {
latestMessage: 'desc'
}
},
aggs: {
latestMessage: {
max: {
field: 'timestamp'
}
}
}
}
}
}
}
}
}
}
})
.then((resp) => {
let messagesPerJob = [];
if (resp.hits.total !== 0 &&
resp.aggregations &&
resp.aggregations.levelsPerJob &&
resp.aggregations.levelsPerJob.buckets &&
resp.aggregations.levelsPerJob.buckets.length) {
messagesPerJob = resp.aggregations.levelsPerJob.buckets;
}
resolve({ messagesPerJob });
})
.catch((resp) => {
reject(resp);
});
});
}
return {
getJobAuditMessages,
getAuditMessagesSummary
};
}

View file

@ -11,19 +11,21 @@ import angular from 'angular';
import moment from 'moment'; import moment from 'moment';
import { parseInterval } from 'ui/utils/parse_interval'; import { parseInterval } from 'ui/utils/parse_interval';
import { ml } from 'plugins/ml/services/ml_api_service';
import { FieldsServiceProvider } from 'plugins/ml/services/fields_service';
import { labelDuplicateDetectorDescriptions } from 'plugins/ml/util/anomaly_utils'; import { labelDuplicateDetectorDescriptions } from 'plugins/ml/util/anomaly_utils';
import { isWebUrl } from 'plugins/ml/util/string_utils'; import { isWebUrl } from 'plugins/ml/util/string_utils';
import { ML_DATA_PREVIEW_COUNT } from 'plugins/ml/../common/util/job_utils'; import { ML_DATA_PREVIEW_COUNT } from 'plugins/ml/../common/util/job_utils';
import { uiModules } from 'ui/modules'; let jobService = undefined;
const module = uiModules.get('apps/ml');
module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessageBarService, Private) { export function JobServiceProvider($q, es, Private, mlMessageBarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
let jobs = []; let jobs = [];
let datafeedIds = {}; let datafeedIds = {};
class JobService {
constructor() {
this.currentJob = undefined; this.currentJob = undefined;
this.jobs = []; this.jobs = [];
@ -42,28 +44,9 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
activeDatafeeds: { label: 'Active datafeeds', value: 0, show: true } activeDatafeeds: { label: 'Active datafeeds', value: 0, show: true }
}; };
this.jobUrls = {}; this.jobUrls = {};
// private function used to check the job saving response
function checkSaveResponse(resp, origJob) {
if (resp) {
if (resp.job_id) {
if (resp.job_id === origJob.job_id) {
console.log('checkSaveResponse(): save successful');
return true;
}
} else {
if (resp.errorCode) {
console.log('checkSaveResponse(): save failed', resp);
return false;
}
}
} else {
console.log('checkSaveResponse(): response is empty');
return false;
}
} }
this.getBlankJob = function () { getBlankJob() {
return { return {
job_id: '', job_id: '',
description: '', description: '',
@ -81,10 +64,10 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
format: 'delimited' format: 'delimited'
} }
}; };
}; }
this.loadJobs = function () { loadJobs() {
const deferred = $q.defer(); return $q((resolve, reject) => {
jobs = []; jobs = [];
datafeedIds = {}; datafeedIds = {};
@ -135,7 +118,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
this.jobs = jobs; this.jobs = jobs;
createJobStats(this.jobs, this.jobStats); createJobStats(this.jobs, this.jobStats);
createJobUrls(this.jobs, this.jobUrls); createJobUrls(this.jobs, this.jobUrls);
deferred.resolve({ jobs: this.jobs }); resolve({ jobs: this.jobs });
}); });
}) })
.catch((err) => { .catch((err) => {
@ -146,16 +129,16 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
}); });
function error(err) { function error(err) {
console.log('MlJobsList error getting list of jobs:', err); console.log('jobService error getting list of jobs:', err);
msgs.error('Jobs list could not be retrieved'); msgs.error('Jobs list could not be retrieved');
msgs.error('', err); msgs.error('', err);
deferred.reject({ jobs, err }); reject({ jobs, err });
}
});
} }
return deferred.promise;
};
this.refreshJob = function (jobId) { refreshJob(jobId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
ml.jobs({ jobId }) ml.jobs({ jobId })
.then((resp) => { .then((resp) => {
console.log('refreshJob query response:', resp); console.log('refreshJob query response:', resp);
@ -208,7 +191,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
this.jobs = jobs; this.jobs = jobs;
createJobStats(this.jobs, this.jobStats); createJobStats(this.jobs, this.jobStats);
createJobUrls(this.jobs, this.jobUrls); createJobUrls(this.jobs, this.jobUrls);
deferred.resolve({ jobs: this.jobs }); resolve({ jobs: this.jobs });
}); });
}) })
.catch((err) => { .catch((err) => {
@ -220,16 +203,16 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
}); });
function error(err) { function error(err) {
console.log('MlJobsList error getting list of jobs:', err); console.log('JobService error getting list of jobs:', err);
msgs.error('Jobs list could not be retrieved'); msgs.error('Jobs list could not be retrieved');
msgs.error('', err); msgs.error('', err);
deferred.reject({ jobs, err }); reject({ jobs, err });
}
});
} }
return deferred.promise;
};
this.loadDatafeeds = function (datafeedId) { loadDatafeeds(datafeedId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const datafeeds = []; const datafeeds = [];
const sId = (datafeedId !== undefined) ? { datafeed_id: datafeedId } : undefined; const sId = (datafeedId !== undefined) ? { datafeed_id: datafeedId } : undefined;
@ -252,7 +235,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} }
} }
} }
deferred.resolve({ datafeeds }); resolve({ datafeeds });
}) })
.catch((err) => { .catch((err) => {
error(err); error(err);
@ -265,16 +248,14 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
console.log('loadDatafeeds error getting list of datafeeds:', err); console.log('loadDatafeeds error getting list of datafeeds:', err);
msgs.error('datafeeds list could not be retrieved'); msgs.error('datafeeds list could not be retrieved');
msgs.error('', err); msgs.error('', err);
deferred.reject({ jobs, err }); reject({ jobs, err });
}
});
} }
return deferred.promise;
};
updateSingleJobCounts(jobId) {
return $q((resolve, reject) => {
this.updateSingleJobCounts = function (jobId) { console.log('jobService: update job counts and state for ' + jobId);
const deferred = $q.defer();
console.log('mlJobService: update job counts and state for ' + jobId);
ml.jobStats({ jobId }) ml.jobStats({ jobId })
.then((resp) => { .then((resp) => {
console.log('updateSingleJobCounts controller query response:', resp); console.log('updateSingleJobCounts controller query response:', resp);
@ -315,13 +296,13 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} }
createJobStats(this.jobs, this.jobStats); createJobStats(this.jobs, this.jobStats);
createJobUrls(this.jobs, this.jobUrls); createJobUrls(this.jobs, this.jobUrls);
deferred.resolve({ jobs: this.jobs }); resolve({ jobs: this.jobs });
}) })
.catch((err) => { .catch((err) => {
error(err); error(err);
}); });
} else { } else {
deferred.resolve({ jobs: this.jobs }); resolve({ jobs: this.jobs });
} }
}).catch((err) => { }).catch((err) => {
@ -332,15 +313,15 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
console.log('updateSingleJobCounts error getting job details:', err); console.log('updateSingleJobCounts error getting job details:', err);
msgs.error('Job details could not be retrieved for ' + jobId); msgs.error('Job details could not be retrieved for ' + jobId);
msgs.error('', err); msgs.error('', err);
deferred.reject({ jobs, err }); reject({ jobs, err });
} }
return deferred.promise; });
}; }
this.updateAllJobStats = function () { updateAllJobStats() {
const deferred = $q.defer(); return $q((resolve, reject) => {
console.log('mlJobService: update all jobs counts and state'); console.log('jobService: update all jobs counts and state');
ml.jobStats().then((resp) => { ml.jobStats().then((resp) => {
console.log('updateAllJobStats controller query response:', resp); console.log('updateAllJobStats controller query response:', resp);
let newJobsAdded = false; let newJobsAdded = false;
@ -403,7 +384,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
if (newJobsAdded || resp.jobs.length !== jobs.length) { if (newJobsAdded || resp.jobs.length !== jobs.length) {
console.log('updateAllJobStats: number of jobs differs. reloading all jobs'); console.log('updateAllJobStats: number of jobs differs. reloading all jobs');
this.loadJobs().then(() => { this.loadJobs().then(() => {
deferred.resolve({ jobs: this.jobs, listChanged: true }); resolve({ jobs: this.jobs, listChanged: true });
}) })
.catch((err) => { .catch((err) => {
error(err); error(err);
@ -411,7 +392,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} else { } else {
createJobStats(this.jobs, this.jobStats); createJobStats(this.jobs, this.jobStats);
createJobUrls(this.jobs, this.jobUrls); createJobUrls(this.jobs, this.jobUrls);
deferred.resolve({ jobs: this.jobs, listChanged: false }); resolve({ jobs: this.jobs, listChanged: false });
} }
}) })
.catch((err) => { .catch((err) => {
@ -426,13 +407,13 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
console.log('updateAllJobStats error getting list job details:', err); console.log('updateAllJobStats error getting list job details:', err);
msgs.error('Job details could not be retrieved'); msgs.error('Job details could not be retrieved');
msgs.error('', err); msgs.error('', err);
deferred.reject({ jobs, err }); reject({ jobs, err });
} }
return deferred.promise; });
}; }
this.getRunningJobs = function () { getRunningJobs() {
const runningJobs = []; const runningJobs = [];
_.each(jobs, (job) => { _.each(jobs, (job) => {
if (job.datafeed_config && job.datafeed_config.state === 'started') { if (job.datafeed_config && job.datafeed_config.state === 'started') {
@ -440,10 +421,10 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} }
}); });
return runningJobs; return runningJobs;
}; }
this.updateSingleJobDatafeedState = function (jobId) { updateSingleJobDatafeedState(jobId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const datafeedId = this.getDatafeedId(jobId); const datafeedId = this.getDatafeedId(jobId);
@ -455,30 +436,30 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
if (datafeeds && datafeeds.length) { if (datafeeds && datafeeds.length) {
state = datafeeds[0].state; state = datafeeds[0].state;
} }
deferred.resolve(state); resolve(state);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
this.saveNewJob = function (job) { saveNewJob(job) {
// run then and catch through the same check // run then and catch through the same check
const func = function (resp) { function func(resp) {
console.log('Response for job query:', resp); console.log('Response for job query:', resp);
const success = checkSaveResponse(resp, job); const success = checkSaveResponse(resp, job);
return { success, job, resp }; return { success, job, resp };
}; }
// return the promise chain // return the promise chain
return ml.addJob({ jobId: job.job_id, job }) return $q.when(ml.addJob({ jobId: job.job_id, job }))
.then(func).catch(func); .then(func).catch(func);
}; }
this.deleteJob = function (job, statusIn) { deleteJob(job, statusIn) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const status = statusIn || { deleteDatafeed: 0, deleteJob: 0, errorMessage: '' }; const status = statusIn || { deleteDatafeed: 0, deleteJob: 0, errorMessage: '' };
// chain of endpoint calls to delete a job. // chain of endpoint calls to delete a job.
@ -504,7 +485,7 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
ml.forceDeleteJob({ jobId: job.job_id }) ml.forceDeleteJob({ jobId: job.job_id })
.then(() => { .then(() => {
status.deleteJob = 1; status.deleteJob = 1;
deferred.resolve({ success: true }); resolve({ success: true });
}) })
.catch((resp) => { .catch((resp) => {
status.deleteJob = -1; status.deleteJob = -1;
@ -516,13 +497,13 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
if (resp.statusCode === 500) { if (resp.statusCode === 500) {
status.errorMessage = txt; status.errorMessage = txt;
} }
deferred.reject({ success: false }); reject({ success: false });
} }
return deferred.promise; });
}; }
this.cloneJob = function (job) { cloneJob(job) {
// create a deep copy of a job object // create a deep copy of a job object
// also remove items from the job which are set by the server and not needed // also remove items from the job which are set by the server and not needed
// in the future this formatting could be optional // in the future this formatting could be optional
@ -571,11 +552,11 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} }
return tempJob; return tempJob;
}; }
this.updateJob = function (jobId, job) { updateJob(jobId, job) {
// return the promise chain // return the promise chain
return ml.updateJob({ jobId, job }) return $q.when(ml.updateJob({ jobId, job }))
.then((resp) => { .then((resp) => {
console.log('update job', resp); console.log('update job', resp);
return { success: true }; return { success: true };
@ -584,11 +565,11 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
console.log('update job', err); console.log('update job', err);
return { success: false, message: err.message }; return { success: false, message: err.message };
}); });
}; }
this.validateJob = function (obj) { validateJob(obj) {
// return the promise chain // return the promise chain
return ml.validateJob(obj) return $q.when(ml.validateJob(obj))
.then((messages) => { .then((messages) => {
console.log('validate job', messages); console.log('validate job', messages);
return { success: true, messages }; return { success: true, messages };
@ -603,24 +584,24 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
}] }]
}; };
}); });
}; }
// find a job based on the id // find a job based on the id
this.getJob = function (jobId) { getJob(jobId) {
const job = _.find(jobs, (j) => { const job = _.find(jobs, (j) => {
return j.job_id === jobId; return j.job_id === jobId;
}); });
return job; return job;
}; }
// use elasticsearch to load basic information on jobs, as used by various result // use elasticsearch to load basic information on jobs, as used by various result
// dashboards in the Ml plugin. Returned response contains a jobs property, // dashboards in the Ml plugin. Returned response contains a jobs property,
// which is an array of objects containing id, description, bucketSpanSeconds, detectors // which is an array of objects containing id, description, bucketSpanSeconds, detectors
// and detectorDescriptions properties, plus a customUrls key if custom URLs // and detectorDescriptions properties, plus a customUrls key if custom URLs
// have been configured for the job. // have been configured for the job.
this.getBasicJobInfo = function () { getBasicJobInfo() {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { success: true, jobs: [] }; const obj = { success: true, jobs: [] };
ml.jobs() ml.jobs()
@ -628,15 +609,15 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
if (resp.jobs && resp.jobs.length > 0) { if (resp.jobs && resp.jobs.length > 0) {
obj.jobs = processBasicJobInfo(this, resp.jobs); obj.jobs = processBasicJobInfo(this, resp.jobs);
} }
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
console.log('getBasicJobInfo error getting list of jobs:', resp); console.log('getBasicJobInfo error getting list of jobs:', resp);
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
// Obtains the list of fields by which record level results may be viewed for all // Obtains the list of fields by which record level results may be viewed for all
// the jobs that have been created. Essentially this is the list of unique 'by', // the jobs that have been created. Essentially this is the list of unique 'by',
@ -648,8 +629,8 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
// for that job. // for that job.
// Contains an addition '*' key which holds an array of the // Contains an addition '*' key which holds an array of the
// unique fields across all jobs. // unique fields across all jobs.
this.getJobViewByFields = function () { getJobViewByFields() {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { success: true, fieldsByJob: { '*': [] } }; const obj = { success: true, fieldsByJob: { '*': [] } };
ml.jobs() ml.jobs()
@ -688,20 +669,20 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
}); });
} }
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
console.log('getJobViewByFields error getting list of viewBy fields:', resp); console.log('getJobViewByFields error getting list of viewBy fields:', resp);
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
// search to load a few records to extract the time field // search to load a few records to extract the time field
this.searchTimeFields = function (index, type, field) { searchTimeFields(index, type, field) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { time: '' }; const obj = { time: '' };
es.search({ es.search({
@ -718,16 +699,16 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
obj.time = hit._source[field]; obj.time = hit._source[field];
} }
} }
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
}; }
this.searchPreview = function (job) { searchPreview(job) {
const deferred = $q.defer(); return $q((resolve, reject) => {
if (job.datafeed_config) { if (job.datafeed_config) {
@ -749,11 +730,11 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
// This time filter is required for datafeed searches using aggregations to ensure // This time filter is required for datafeed searches using aggregations to ensure
// the search does not create too many buckets (default 10000 max_bucket limit), // the search does not create too many buckets (default 10000 max_bucket limit),
// but apply it to searches without aggregations too for consistency. // but apply it to searches without aggregations too for consistency.
const fieldsService = Private(FieldsServiceProvider); ml.getTimeFieldRange({
fieldsService.getTimeFieldRange( index: job.datafeed_config.indices,
job.datafeed_config.indices, timeFieldName: job.data_description.time_field,
job.data_description.time_field, query
query) })
.then((timeRange) => { .then((timeRange) => {
const bucketSpan = parseInterval(job.analysis_config.bucket_span); const bucketSpan = parseInterval(job.analysis_config.bucket_span);
const earliestMs = timeRange.start.epoch; const earliestMs = timeRange.start.epoch;
@ -856,67 +837,68 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
es.search(data) es.search(data)
.then((resp) => { .then((resp) => {
deferred.resolve(resp); resolve(resp);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
} }
return deferred.promise; });
}; }
this.openJob = function (jobId) { openJob(jobId) {
return ml.openJob({ jobId }); return $q.when(ml.openJob({ jobId }));
}; }
this.closeJob = function (jobId) { closeJob(jobId) {
return ml.closeJob({ jobId }); return $q.when(ml.closeJob({ jobId }));
}; }
this.forceCloseJob = function (jobId) { forceCloseJob(jobId) {
return ml.forceCloseJob({ jobId }); return $q.when(ml.forceCloseJob({ jobId }));
}; }
this.saveNewDatafeed = function (datafeedConfig, jobId) { saveNewDatafeed(datafeedConfig, jobId) {
const datafeedId = 'datafeed-' + jobId; const datafeedId = `datafeed-${jobId}`;
datafeedConfig.job_id = jobId; datafeedConfig.job_id = jobId;
return ml.addDatafeed({ return $q.when(ml.addDatafeed({
datafeedId, datafeedId,
datafeedConfig datafeedConfig
}); }));
}; }
this.updateDatafeed = function (datafeedId, datafeedConfig) { updateDatafeed(datafeedId, datafeedConfig) {
return ml.updateDatafeed({ datafeedId, datafeedConfig }) return $q.when(ml.updateDatafeed({ datafeedId, datafeedConfig }))
.then((resp) => { .then((resp) => {
console.log('update datafeed', resp); console.log('update datafeed', resp);
return { success: true }; return { success: true };
}).catch((err) => { })
.catch((err) => {
msgs.error('Could not update datafeed: ' + datafeedId); msgs.error('Could not update datafeed: ' + datafeedId);
console.log('update datafeed', err); console.log('update datafeed', err);
return { success: false, message: err.message }; return { success: false, message: err.message };
}); });
}; }
this.deleteDatafeed = function () { deleteDatafeed() {
}; }
// start the datafeed for a given job // start the datafeed for a given job
// refresh the job state on start success // refresh the job state on start success
this.startDatafeed = function (datafeedId, jobId, start, end) { startDatafeed(datafeedId, jobId, start, end) {
const deferred = $q.defer(); return $q((resolve, reject) => {
// if the end timestamp is a number, add one ms to it to make it // if the end timestamp is a number, add one ms to it to make it
// inclusive of the end of the data // inclusive of the end of the data
@ -930,74 +912,116 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
end end
}) })
.then((resp) => { .then((resp) => {
deferred.resolve(resp); resolve(resp);
})
}).catch((err) => { .catch((err) => {
console.log('MlJobsList error starting datafeed:', err); console.log('jobService error starting datafeed:', err);
msgs.error('Could not start datafeed for ' + jobId, err); msgs.error('Could not start datafeed for ' + jobId, err);
deferred.reject(err); reject(err);
}); });
return deferred.promise; });
}; }
// stop the datafeed for a given job // stop the datafeed for a given job
// refresh the job state on stop success // refresh the job state on stop success
this.stopDatafeed = function (datafeedId, jobId) { stopDatafeed(datafeedId, jobId) {
const deferred = $q.defer(); return $q((resolve, reject) => {
ml.stopDatafeed({ ml.stopDatafeed({
datafeedId datafeedId
}) })
.then((resp) => { .then((resp) => {
deferred.resolve(resp); resolve(resp);
})
}).catch((err) => { .catch((err) => {
console.log('MlJobsList error stopping datafeed:', err); console.log('jobService error stopping datafeed:', err);
if (err.statusCode === 500) { if (err.statusCode === 500) {
msgs.error('Could not stop datafeed for ' + jobId); msgs.error('Could not stop datafeed for ' + jobId);
msgs.error('Request may have timed out and may still be running in the background.'); msgs.error('Request may have timed out and may still be running in the background.');
} else { } else {
msgs.error('Could not stop datafeed for ' + jobId, err); msgs.error('Could not stop datafeed for ' + jobId, err);
} }
deferred.reject(err); reject(err);
}); });
return deferred.promise; });
}; }
this.validateDetector = function (detector) { validateDetector(detector) {
const deferred = $q.defer(); return $q((resolve, reject) => {
if (detector) { if (detector) {
ml.validateDetector({ detector }) ml.validateDetector({ detector })
.then((resp) => { .then((resp) => {
deferred.resolve(resp); resolve(resp);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
} else { } else {
deferred.reject({}); reject({});
}
});
} }
return deferred.promise;
};
this.getDatafeedId = function (jobId) { getDatafeedId(jobId) {
let datafeedId = datafeedIds[jobId]; let datafeedId = datafeedIds[jobId];
if (datafeedId === undefined) { if (datafeedId === undefined) {
datafeedId = 'datafeed-' + jobId; datafeedId = `datafeed-${jobId}`;
} }
return datafeedId; return datafeedId;
}; }
this.getDatafeedPreview = function (jobId) { getDatafeedPreview(jobId) {
const datafeedId = this.getDatafeedId(jobId); const datafeedId = this.getDatafeedId(jobId);
return ml.datafeedPreview({ datafeedId }); return $q.when(ml.datafeedPreview({ datafeedId }));
}; }
function processBasicJobInfo(mlJobService, jobsList) { // get the list of job group ids as well as how many jobs are in each group
getJobGroups() {
const groups = [];
const tempGroups = {};
this.jobs.forEach(job => {
if (Array.isArray(job.groups)) {
job.groups.forEach(group => {
if (tempGroups[group] === undefined) {
tempGroups[group] = [job];
} else {
tempGroups[group].push(job);
}
});
}
});
_.each(tempGroups, (js, id) => {
groups.push({ id, jobs: js });
});
return groups;
}
}
// private function used to check the job saving response
function checkSaveResponse(resp, origJob) {
if (resp) {
if (resp.job_id) {
if (resp.job_id === origJob.job_id) {
console.log('checkSaveResponse(): save successful');
return true;
}
} else {
if (resp.errorCode) {
console.log('checkSaveResponse(): save failed', resp);
return false;
}
}
} else {
console.log('checkSaveResponse(): response is empty');
return false;
}
}
function processBasicJobInfo(localJobService, jobsList) {
// Process the list of job data obtained from the jobs endpoint to return // Process the list of job data obtained from the jobs endpoint to return
// an array of objects containing the basic information (id, description, bucketSpan, detectors // an array of objects containing the basic information (id, description, bucketSpan, detectors
// and detectorDescriptions properties, plus a customUrls key if custom URLs // and detectorDescriptions properties, plus a customUrls key if custom URLs
// have been configured for the job) used by various result dashboards in the ml plugin. // have been configured for the job) used by various result dashboards in the ml plugin.
// The key information is stored in the mlJobService object for quick access. // The key information is stored in the jobService object for quick access.
const processedJobsList = []; const processedJobsList = [];
let detectorDescriptionsByJob = {}; let detectorDescriptionsByJob = {};
const detectorsByJob = {}; const detectorsByJob = {};
@ -1047,10 +1071,10 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
} }
} }
mlJobService.jobDescriptions[job.id] = job.description; localJobService.jobDescriptions[job.id] = job.description;
detectorDescriptionsByJob[job.id] = job.detectorDescriptions; detectorDescriptionsByJob[job.id] = job.detectorDescriptions;
detectorsByJob[job.id] = job.detectors; detectorsByJob[job.id] = job.detectors;
mlJobService.basicJobs[job.id] = job; localJobService.basicJobs[job.id] = job;
processedJobsList.push(job); processedJobsList.push(job);
}); });
@ -1060,8 +1084,8 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
dtr.detector_description = detectorDescriptionsByJob[jobId][i]; dtr.detector_description = detectorDescriptionsByJob[jobId][i];
}); });
}); });
mlJobService.detectorsByJob = detectorsByJob; localJobService.detectorsByJob = detectorsByJob;
mlJobService.customUrlsByJob = customUrlsByJob; localJobService.customUrlsByJob = customUrlsByJob;
return processedJobsList; return processedJobsList;
} }
@ -1131,25 +1155,8 @@ module.service('mlJobService', function ($rootScope, $http, $q, es, ml, mlMessag
}); });
} }
// get the list of job group ids as well as how many jobs are in each group if (jobService === undefined) {
this.getJobGroups = function () { jobService = new JobService();
const groups = [];
const tempGroups = {};
this.jobs.forEach(job => {
if (Array.isArray(job.groups)) {
job.groups.forEach(group => {
if (tempGroups[group] === undefined) {
tempGroups[group] = [job];
} else {
tempGroups[group].push(job);
} }
}); return jobService;
} }
});
_.each(tempGroups, (js, id) => {
groups.push({ id, jobs: js });
});
return groups;
};
});

View file

@ -8,15 +8,12 @@
import _ from 'lodash'; import _ from 'lodash';
import { uiModules } from 'ui/modules'; import { ml } from 'plugins/ml/services/ml_api_service';
const module = uiModules.get('apps/ml');
module.service('mlESMappingService', function ($q, ml) { // Returns the mapping type of the specified field.
// Accepts fieldName containing dots representing a nested sub-field.
// Returns the mapping type of the specified field. export function getFieldTypeFromMapping(index, fieldName) {
// Accepts fieldName containing dots representing a nested sub-field. return new Promise((resolve, reject) => {
this.getFieldTypeFromMapping = function (index, fieldName) {
return $q((resolve, reject) => {
if (index !== '') { if (index !== '') {
ml.getFieldCaps({ index, fields: [fieldName] }) ml.getFieldCaps({ index, fields: [fieldName] })
.then((resp) => { .then((resp) => {
@ -37,5 +34,4 @@ module.service('mlESMappingService', function ($q, ml) {
reject(); reject();
} }
}); });
}; }
});

View file

@ -7,142 +7,139 @@
import { pick } from 'lodash'; import { pick } from 'lodash';
import './http_service';
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules'; import { http } from './http_service';
const module = uiModules.get('apps/ml');
module.service('ml', function (prlHttpService) { const basePath = chrome.addBasePath('/api/ml');
const http = prlHttpService;
const basePath = chrome.addBasePath('/api/ml');
this.jobs = function (obj) { export const ml = {
jobs(obj) {
const jobId = (obj && obj.jobId) ? `/${obj.jobId}` : ''; const jobId = (obj && obj.jobId) ? `/${obj.jobId}` : '';
return http.request({ return http({
url: `${basePath}/anomaly_detectors${jobId}`, url: `${basePath}/anomaly_detectors${jobId}`,
}); });
}; },
this.jobStats = function (obj) { jobStats(obj) {
const jobId = (obj && obj.jobId) ? `/${obj.jobId}` : ''; const jobId = (obj && obj.jobId) ? `/${obj.jobId}` : '';
return http.request({ return http({
url: `${basePath}/anomaly_detectors${jobId}/_stats`, url: `${basePath}/anomaly_detectors${jobId}/_stats`,
}); });
}; },
this.addJob = function (obj) { addJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}`, url: `${basePath}/anomaly_detectors/${obj.jobId}`,
method: 'PUT', method: 'PUT',
data: obj.job data: obj.job
}); });
}; },
this.openJob = function (obj) { openJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/_open`, url: `${basePath}/anomaly_detectors/${obj.jobId}/_open`,
method: 'POST' method: 'POST'
}); });
}; },
this.closeJob = function (obj) { closeJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/_close`, url: `${basePath}/anomaly_detectors/${obj.jobId}/_close`,
method: 'POST' method: 'POST'
}); });
}; },
this.forceCloseJob = function (obj) { forceCloseJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/_close?force=true`, url: `${basePath}/anomaly_detectors/${obj.jobId}/_close?force=true`,
method: 'POST' method: 'POST'
}); });
}; },
this.deleteJob = function (obj) { deleteJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}`, url: `${basePath}/anomaly_detectors/${obj.jobId}`,
method: 'DELETE' method: 'DELETE'
}); });
}; },
this.forceDeleteJob = function (obj) { forceDeleteJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}?force=true`, url: `${basePath}/anomaly_detectors/${obj.jobId}?force=true`,
method: 'DELETE' method: 'DELETE'
}); });
}; },
this.updateJob = function (obj) { updateJob(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/_update`, url: `${basePath}/anomaly_detectors/${obj.jobId}/_update`,
method: 'POST', method: 'POST',
data: obj.job data: obj.job
}); });
}; },
this.estimateBucketSpan = function (obj) { estimateBucketSpan(obj) {
return http.request({ return http({
url: `${basePath}/validate/estimate_bucket_span`, url: `${basePath}/validate/estimate_bucket_span`,
method: 'POST', method: 'POST',
data: obj data: obj
}); });
}; },
this.validateJob = function (obj) { validateJob(obj) {
return http.request({ return http({
url: `${basePath}/validate/job`, url: `${basePath}/validate/job`,
method: 'POST', method: 'POST',
data: obj data: obj
}); });
}; },
this.datafeeds = function (obj) { datafeeds(obj) {
const datafeedId = (obj && obj.datafeedId) ? `/${obj.datafeedId}` : ''; const datafeedId = (obj && obj.datafeedId) ? `/${obj.datafeedId}` : '';
return http.request({ return http({
url: `${basePath}/datafeeds${datafeedId}`, url: `${basePath}/datafeeds${datafeedId}`,
}); });
}; },
this.datafeedStats = function (obj) { datafeedStats(obj) {
const datafeedId = (obj && obj.datafeedId) ? `/${obj.datafeedId}` : ''; const datafeedId = (obj && obj.datafeedId) ? `/${obj.datafeedId}` : '';
return http.request({ return http({
url: `${basePath}/datafeeds${datafeedId}/_stats`, url: `${basePath}/datafeeds${datafeedId}/_stats`,
}); });
}; },
this.addDatafeed = function (obj) { addDatafeed(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}`, url: `${basePath}/datafeeds/${obj.datafeedId}`,
method: 'PUT', method: 'PUT',
data: obj.datafeedConfig data: obj.datafeedConfig
}); });
}; },
this.updateDatafeed = function (obj) { updateDatafeed(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}/_update`, url: `${basePath}/datafeeds/${obj.datafeedId}/_update`,
method: 'POST', method: 'POST',
data: obj.datafeedConfig data: obj.datafeedConfig
}); });
}; },
this.deleteDatafeed = function (obj) { deleteDatafeed(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}`, url: `${basePath}/datafeeds/${obj.datafeedId}`,
method: 'DELETE' method: 'DELETE'
}); });
}; },
this.forceDeleteDatafeed = function (obj) { forceDeleteDatafeed(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}?force=true`, url: `${basePath}/datafeeds/${obj.datafeedId}?force=true`,
method: 'DELETE' method: 'DELETE'
}); });
}; },
this.startDatafeed = function (obj) { startDatafeed(obj) {
const data = {}; const data = {};
if(obj.start !== undefined) { if(obj.start !== undefined) {
data.start = obj.start; data.start = obj.start;
@ -150,78 +147,78 @@ module.service('ml', function (prlHttpService) {
if(obj.end !== undefined) { if(obj.end !== undefined) {
data.end = obj.end; data.end = obj.end;
} }
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}/_start`, url: `${basePath}/datafeeds/${obj.datafeedId}/_start`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.stopDatafeed = function (obj) { stopDatafeed(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}/_stop`, url: `${basePath}/datafeeds/${obj.datafeedId}/_stop`,
method: 'POST' method: 'POST'
}); });
}; },
this.datafeedPreview = function (obj) { datafeedPreview(obj) {
return http.request({ return http({
url: `${basePath}/datafeeds/${obj.datafeedId}/_preview`, url: `${basePath}/datafeeds/${obj.datafeedId}/_preview`,
method: 'GET' method: 'GET'
}); });
}; },
this.validateDetector = function (obj) { validateDetector(obj) {
return http.request({ return http({
url: `${basePath}/anomaly_detectors/_validate/detector`, url: `${basePath}/anomaly_detectors/_validate/detector`,
method: 'POST', method: 'POST',
data: obj.detector data: obj.detector
}); });
}; },
this.forecast = function (obj) { forecast(obj) {
const data = {}; const data = {};
if(obj.duration !== undefined) { if(obj.duration !== undefined) {
data.duration = obj.duration; data.duration = obj.duration;
} }
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/_forecast`, url: `${basePath}/anomaly_detectors/${obj.jobId}/_forecast`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.overallBuckets = function (obj) { overallBuckets(obj) {
const data = pick(obj, [ const data = pick(obj, [
'topN', 'topN',
'bucketSpan', 'bucketSpan',
'start', 'start',
'end' 'end'
]); ]);
return http.request({ return http({
url: `${basePath}/anomaly_detectors/${obj.jobId}/results/overall_buckets`, url: `${basePath}/anomaly_detectors/${obj.jobId}/results/overall_buckets`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.checkPrivilege = function (obj) { checkPrivilege(obj) {
return http.request({ return http({
url: `${basePath}/_has_privileges`, url: `${basePath}/_has_privileges`,
method: 'POST', method: 'POST',
data: obj data: obj
}); });
}; },
this.getNotificationSettings = function () { getNotificationSettings() {
return http.request({ return http({
url: `${basePath}/notification_settings`, url: `${basePath}/notification_settings`,
method: 'GET' method: 'GET'
}); });
}; },
this.getFieldCaps = function (obj) { getFieldCaps(obj) {
const data = {}; const data = {};
if(obj.index !== undefined) { if(obj.index !== undefined) {
data.index = obj.index; data.index = obj.index;
@ -229,28 +226,28 @@ module.service('ml', function (prlHttpService) {
if(obj.fields !== undefined) { if(obj.fields !== undefined) {
data.fields = obj.fields; data.fields = obj.fields;
} }
return http.request({ return http({
url: `${basePath}/indices/field_caps`, url: `${basePath}/indices/field_caps`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.recognizeIndex = function (obj) { recognizeIndex(obj) {
return http.request({ return http({
url: `${basePath}/modules/recognize/${obj.indexPatternTitle}`, url: `${basePath}/modules/recognize/${obj.indexPatternTitle}`,
method: 'GET' method: 'GET'
}); });
}; },
this.getDataRecognizerModule = function (obj) { getDataRecognizerModule(obj) {
return http.request({ return http({
url: `${basePath}/modules/get_module/${obj.moduleId}`, url: `${basePath}/modules/get_module/${obj.moduleId}`,
method: 'GET' method: 'GET'
}); });
}; },
this.setupDataRecognizerConfig = function (obj) { setupDataRecognizerConfig(obj) {
const data = pick(obj, [ const data = pick(obj, [
'prefix', 'prefix',
'groups', 'groups',
@ -258,14 +255,14 @@ module.service('ml', function (prlHttpService) {
'query' 'query'
]); ]);
return http.request({ return http({
url: `${basePath}/modules/setup/${obj.moduleId}`, url: `${basePath}/modules/setup/${obj.moduleId}`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.getVisualizerFieldStats = function (obj) { getVisualizerFieldStats(obj) {
const data = pick(obj, [ const data = pick(obj, [
'query', 'query',
'timeFieldName', 'timeFieldName',
@ -277,14 +274,14 @@ module.service('ml', function (prlHttpService) {
'maxExamples' 'maxExamples'
]); ]);
return http.request({ return http({
url: `${basePath}/data_visualizer/get_field_stats/${obj.indexPatternTitle}`, url: `${basePath}/data_visualizer/get_field_stats/${obj.indexPatternTitle}`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.getVisualizerOverallStats = function (obj) { getVisualizerOverallStats(obj) {
const data = pick(obj, [ const data = pick(obj, [
'query', 'query',
'timeFieldName', 'timeFieldName',
@ -295,61 +292,60 @@ module.service('ml', function (prlHttpService) {
'nonAggregatableFields' 'nonAggregatableFields'
]); ]);
return http.request({ return http({
url: `${basePath}/data_visualizer/get_overall_stats/${obj.indexPatternTitle}`, url: `${basePath}/data_visualizer/get_overall_stats/${obj.indexPatternTitle}`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.calendars = function (obj) { calendars(obj) {
const calendarId = (obj && obj.calendarId) ? `/${obj.calendarId}` : ''; const calendarId = (obj && obj.calendarId) ? `/${obj.calendarId}` : '';
return http.request({ return http({
url: `${basePath}/calendars${calendarId}`, url: `${basePath}/calendars${calendarId}`,
method: 'GET' method: 'GET'
}); });
}; },
addCalendar(obj) {
this.addCalendar = function (obj) { return http({
return http.request({
url: `${basePath}/calendars`, url: `${basePath}/calendars`,
method: 'PUT', method: 'PUT',
data: obj data: obj
}); });
}; },
this.updateCalendar = function (obj) { updateCalendar(obj) {
const calendarId = (obj && obj.calendarId) ? `/${obj.calendarId}` : ''; const calendarId = (obj && obj.calendarId) ? `/${obj.calendarId}` : '';
return http.request({ return http({
url: `${basePath}/calendars${calendarId}`, url: `${basePath}/calendars${calendarId}`,
method: 'PUT', method: 'PUT',
data: obj data: obj
}); });
}; },
this.deleteCalendar = function (obj) { deleteCalendar(obj) {
return http.request({ return http({
url: `${basePath}/calendars/${obj.calendarId}`, url: `${basePath}/calendars/${obj.calendarId}`,
method: 'DELETE' method: 'DELETE'
}); });
}; },
this.mlNodeCount = function () { mlNodeCount() {
return http.request({ return http({
url: `${basePath}/ml_node_count`, url: `${basePath}/ml_node_count`,
method: 'GET' method: 'GET'
}); });
}; },
this.mlInfo = function () { mlInfo() {
return http.request({ return http({
url: `${basePath}/info`, url: `${basePath}/info`,
method: 'GET' method: 'GET'
}); });
}; },
this.calculateModelMemoryLimit = function (obj) { calculateModelMemoryLimit(obj) {
const data = pick(obj, [ const data = pick(obj, [
'indexPattern', 'indexPattern',
'splitFieldName', 'splitFieldName',
@ -361,14 +357,14 @@ module.service('ml', function (prlHttpService) {
'latestMs' 'latestMs'
]); ]);
return http.request({ return http({
url: `${basePath}/validate/calculate_model_memory_limit`, url: `${basePath}/validate/calculate_model_memory_limit`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.getCardinalityOfFields = function (obj) { getCardinalityOfFields(obj) {
const data = pick(obj, [ const data = pick(obj, [
'index', 'index',
'types', 'types',
@ -379,25 +375,24 @@ module.service('ml', function (prlHttpService) {
'latestMs' 'latestMs'
]); ]);
return http.request({ return http({
url: `${basePath}/fields_service/field_cardinality`, url: `${basePath}/fields_service/field_cardinality`,
method: 'POST', method: 'POST',
data data
}); });
}; },
this.getTimeFieldRange = function (obj) { getTimeFieldRange(obj) {
const data = pick(obj, [ const data = pick(obj, [
'index', 'index',
'timeFieldName', 'timeFieldName',
'query' 'query'
]); ]);
return http.request({ return http({
url: `${basePath}/fields_service/time_field_range`, url: `${basePath}/fields_service/time_field_range`,
method: 'POST', method: 'POST',
data data
}); });
}; }
};
});

View file

@ -1,52 +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.
*/
// service for copying text to the users clipboard
// can only work when triggered via a user event, as part of an onclick or ng-click
// returns success
// e.g. mlClipboardService.copy("this could be abused!");
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.service('mlClipboardService', function () {
function copyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = 0;
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
let successful = false;
try {
successful = document.execCommand('copy');
const msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
return successful;
}
this.copy = copyTextToClipboard;
});

View file

@ -1,176 +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.
*/
// Service for carrying out Elasticsearch queries to obtain data for the
// Ml Results dashboards.
import _ from 'lodash';
import { ML_NOTIFICATION_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml');
module.service('mlNotificationService', function ($q, es) {
// search for audit messages, jobId is optional.
// without it, all jobs will be listed.
// fromRange should be a string formatted in ES time units. e.g. 12h, 1d, 7d
this.getJobAuditMessages = function (fromRange, jobId) {
const deferred = $q.defer();
const messages = [];
let jobFilter = {};
// if no jobId specified, load all of the messages
if (jobId !== undefined) {
jobFilter = {
'bool': {
'should': [
{
'term': {
'job_id': '' // catch system messages
}
},
{
'term': {
'job_id': jobId // messages for specified jobId
}
}
]
}
};
}
let timeFilter = {};
if (fromRange !== undefined && fromRange !== '') {
timeFilter = {
'range': {
'timestamp': {
'gte': 'now-' + fromRange,
'lte': 'now'
}
}
};
}
es.search({
index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: 1000,
body:
{
sort: [
{ 'timestamp': { 'order': 'asc' } },
{ 'job_id': { 'order': 'asc' } }
],
'query': {
'bool': {
'filter': [
{
'bool': {
'must_not': {
'term': {
'level': 'activity'
}
}
}
},
jobFilter,
timeFilter
]
}
}
}
})
.then((resp) => {
if (resp.hits.total !== 0) {
_.each(resp.hits.hits, (hit) => {
messages.push(hit._source);
});
}
deferred.resolve({ messages });
})
.catch((resp) => {
deferred.reject(resp);
});
return deferred.promise;
};
// search highest, most recent audit messages for all jobs for the last 24hrs.
this.getAuditMessagesSummary = function () {
const deferred = $q.defer();
const aggs = [];
es.search({
index: ML_NOTIFICATION_INDEX_PATTERN,
ignore_unavailable: true,
size: 0,
body: {
'query': {
'bool': {
'filter': {
'range': {
'timestamp': {
'gte': 'now-1d'
}
}
}
}
},
'aggs': {
'levelsPerJob': {
'terms': {
'field': 'job_id',
},
'aggs': {
'levels': {
'terms': {
'field': 'level',
},
'aggs': {
'latestMessage': {
'terms': {
'field': 'message.raw',
'size': 1,
'order': {
'latestMessage': 'desc'
}
},
'aggs': {
'latestMessage': {
'max': {
'field': 'timestamp'
}
}
}
}
}
}
}
}
}
}
})
.then((resp) => {
if (resp.hits.total !== 0 &&
resp.aggregations &&
resp.aggregations.levelsPerJob &&
resp.aggregations.levelsPerJob.buckets &&
resp.aggregations.levelsPerJob.buckets.length) {
_.each(resp.aggregations.levelsPerJob.buckets, (agg) => {
aggs.push(agg);
});
}
deferred.resolve({ messagesPerJob: aggs });
})
.catch((resp) => {
deferred.reject(resp);
});
return deferred.promise;
};
});

View file

@ -14,16 +14,14 @@ import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_utils';
import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils'; import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils';
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
import { uiModules } from 'ui/modules'; import { ml } from 'plugins/ml/services/ml_api_service';
const module = uiModules.get('apps/ml');
module.service('mlResultsService', function ($q, es, ml) { export function ResultsServiceProvider($q, es) {
// Obtains the maximum bucket anomaly scores by job ID and time.
// Obtains the maximum bucket anomaly scores by job ID and time. // Pass an empty array or ['*'] to search over all job IDs.
// Pass an empty array or ['*'] to search over all job IDs. // Returned response contains a results property, with a key for job
// Returned response contains a results property, with a key for job // which has results for the specified time range.
// which has results for the specified time range. function getScoresByBucket(jobIds, earliestMs, latestMs, interval, maxResults) {
this.getScoresByBucket = function (jobIds, earliestMs, latestMs, interval, maxResults) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
@ -141,13 +139,13 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains a list of scheduled events by job ID and time. // Obtains a list of scheduled events by job ID and time.
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
// Returned response contains a events property, which will only // Returned response contains a events property, which will only
// contains keys for jobs which have scheduled events for the specified time range. // contains keys for jobs which have scheduled events for the specified time range.
this.getScheduledEventsByBucket = function ( function getScheduledEventsByBucket(
jobIds, jobIds,
earliestMs, earliestMs,
latestMs, latestMs,
@ -256,14 +254,14 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s). // Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s).
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
// Returned response contains an influencers property, with a key for each of the influencer field names, // Returned response contains an influencers property, with a key for each of the influencer field names,
// whose value is an array of objects containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. // whose value is an array of objects containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys.
this.getTopInfluencers = function (jobIds, earliestMs, latestMs, maxFieldNames, maxFieldValues) { function getTopInfluencers(jobIds, earliestMs, latestMs, maxFieldNames, maxFieldValues) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, influencers: {} }; const obj = { success: true, influencers: {} };
@ -385,21 +383,20 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the top influencer field values, by maximum anomaly score, for a // Obtains the top influencer field values, by maximum anomaly score, for a
// particular index, field name and job ID(s). // particular index, field name and job ID(s).
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
// Returned response contains a results property, which is an array of objects // Returned response contains a results property, which is an array of objects
// containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. // containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys.
this.getTopInfluencerValues = function (jobIds, influencerFieldName, earliestMs, latestMs, maxResults) { function getTopInfluencerValues(jobIds, influencerFieldName, earliestMs, latestMs, maxResults) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, results: [] }; const obj = { success: true, results: [] };
// Build the criteria to use in the bool filter part of the request. // Build the criteria to use in the bool filter part of the request.
// Adds criteria for the time range plus any specified job IDs. // Adds criteria for the time range plus any specified job IDs.
const boolCriteria = []; const boolCriteria = [{
boolCriteria.push({
range: { range: {
timestamp: { timestamp: {
gte: earliestMs, gte: earliestMs,
@ -407,7 +404,8 @@ module.service('mlResultsService', function ($q, es, ml) {
format: 'epoch_millis' format: 'epoch_millis'
} }
} }
}); }];
if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) {
let jobIdFilterStr = ''; let jobIdFilterStr = '';
_.each(jobIds, (jobId, i) => { _.each(jobIds, (jobId, i) => {
@ -487,12 +485,12 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the overall bucket scores for the specified job ID(s). // Obtains the overall bucket scores for the specified job ID(s).
// Pass ['*'] to search over all job IDs. // Pass ['*'] to search over all job IDs.
// Returned response contains a results property as an object of max score by time. // Returned response contains a results property as an object of max score by time.
this.getOverallBucketScores = function (jobIds, topN, earliestMs, latestMs, interval) { function getOverallBucketScores(jobIds, topN, earliestMs, latestMs, interval) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, results: {} }; const obj = { success: true, results: {} };
@ -518,14 +516,14 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the maximum score by influencer_field_value and by time for the specified job ID(s) // Obtains the maximum score by influencer_field_value and by time for the specified job ID(s)
// (pass an empty array or ['*'] to search over all job IDs), and specified influencer field // (pass an empty array or ['*'] to search over all job IDs), and specified influencer field
// values (pass an empty array to search over all field values). // values (pass an empty array to search over all field values).
// Returned response contains a results property with influencer field values keyed // Returned response contains a results property with influencer field values keyed
// against max score by time. // against max score by time.
this.getInfluencerValueMaxScoreByTime = function ( function getInfluencerValueMaxScoreByTime(
jobIds, jobIds,
influencerFieldName, influencerFieldName,
influencerFieldValues, influencerFieldValues,
@ -666,13 +664,13 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the definition of the category with the specified ID and job ID. // Obtains the definition of the category with the specified ID and job ID.
// Returned response contains four properties - categoryId, regex, examples // Returned response contains four properties - categoryId, regex, examples
// and terms (space delimited String of the common tokens matched in values of the category). // and terms (space delimited String of the common tokens matched in values of the category).
this.getCategoryDefinition = function (jobId, categoryId) { function getCategoryDefinition(jobId, categoryId) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, categoryId: categoryId, terms: null, regex: null, examples: [] }; const obj = { success: true, categoryId: categoryId, terms: null, regex: null, examples: [] };
@ -704,14 +702,14 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Obtains the categorization examples for the categories with the specified IDs // Obtains the categorization examples for the categories with the specified IDs
// from the given index and job ID. // from the given index and job ID.
// Returned response contains two properties - jobId and // Returned response contains two properties - jobId and
// examplesByCategoryId (list of examples against categoryId). // examplesByCategoryId (list of examples against categoryId).
this.getCategoryExamples = function (jobId, categoryIds, maxExamples) { function getCategoryExamples(jobId, categoryIds, maxExamples) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, jobId: jobId, examplesByCategoryId: {} }; const obj = { success: true, jobId: jobId, examplesByCategoryId: {} };
@ -747,7 +745,7 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain record level results containing the influencers // Queries Elasticsearch to obtain record level results containing the influencers
@ -755,7 +753,7 @@ module.service('mlResultsService', function ($q, es, ml) {
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
// Returned response contains a records property, with each record containing // Returned response contains a records property, with each record containing
// only the fields job_id, detector_index, record_score and influencers. // only the fields job_id, detector_index, record_score and influencers.
this.getRecordInfluencers = function (jobIds, threshold, earliestMs, latestMs, maxResults) { function getRecordInfluencers(jobIds, threshold, earliestMs, latestMs, maxResults) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, records: [] }; const obj = { success: true, records: [] };
@ -851,7 +849,7 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain the record level results containing the specified influencer(s), // Queries Elasticsearch to obtain the record level results containing the specified influencer(s),
@ -860,7 +858,7 @@ module.service('mlResultsService', function ($q, es, ml) {
// 'fieldValue' properties. The influencer array uses 'should' for the nested bool query, // 'fieldValue' properties. The influencer array uses 'should' for the nested bool query,
// so this returns record level results which have at least one of the influencers. // so this returns record level results which have at least one of the influencers.
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
this.getRecordsForInfluencer = function (jobIds, influencers, threshold, earliestMs, latestMs, maxResults) { function getRecordsForInfluencer(jobIds, influencers, threshold, earliestMs, latestMs, maxResults) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, records: [] }; const obj = { success: true, records: [] };
@ -957,7 +955,7 @@ module.service('mlResultsService', function ($q, es, ml) {
}, },
sort: [ sort: [
{ record_score: { order: 'desc' } } { record_score: { order: 'desc' } }
], ]
} }
}) })
.then((resp) => { .then((resp) => {
@ -972,13 +970,13 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain the record level results for the specified job and detector, // Queries Elasticsearch to obtain the record level results for the specified job and detector,
// time range, record score threshold, and whether to only return results containing influencers. // time range, record score threshold, and whether to only return results containing influencers.
// An additional, optional influencer field name and value may also be provided. // An additional, optional influencer field name and value may also be provided.
this.getRecordsForDetector = function ( function getRecordsForDetector(
jobId, jobId,
detectorIndex, detectorIndex,
checkForInfluencers, checkForInfluencers,
@ -1098,22 +1096,22 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain all the record level results for the specified job(s), time range, // Queries Elasticsearch to obtain all the record level results for the specified job(s), time range,
// and record score threshold. // and record score threshold.
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
// Returned response contains a records property, which is an array of the matching results. // Returned response contains a records property, which is an array of the matching results.
this.getRecords = function (jobIds, threshold, earliestMs, latestMs, maxResults) { function getRecords(jobIds, threshold, earliestMs, latestMs, maxResults) {
return this.getRecordsForInfluencer(jobIds, [], threshold, earliestMs, latestMs, maxResults); return this.getRecordsForInfluencer(jobIds, [], threshold, earliestMs, latestMs, maxResults);
}; }
// Queries Elasticsearch to obtain the record level results matching the given criteria, // Queries Elasticsearch to obtain the record level results matching the given criteria,
// for the specified job(s), time range, and record score threshold. // for the specified job(s), time range, and record score threshold.
// criteriaFields parameter must be an array, with each object in the array having 'fieldName' // criteriaFields parameter must be an array, with each object in the array having 'fieldName'
// 'fieldValue' properties. // 'fieldValue' properties.
// Pass an empty array or ['*'] to search over all job IDs. // Pass an empty array or ['*'] to search over all job IDs.
this.getRecordsForCriteria = function (jobIds, criteriaFields, threshold, earliestMs, latestMs, maxResults) { function getRecordsForCriteria(jobIds, criteriaFields, threshold, earliestMs, latestMs, maxResults) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { success: true, records: [] }; const obj = { success: true, records: [] };
@ -1202,7 +1200,7 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain metric aggregation results. // Queries Elasticsearch to obtain metric aggregation results.
@ -1213,7 +1211,7 @@ module.service('mlResultsService', function ($q, es, ml) {
// Extra query object can be supplied, or pass null if no additional query // Extra query object can be supplied, or pass null if no additional query
// to that built from the supplied entity fields. // to that built from the supplied entity fields.
// Returned response contains a results property containing the requested aggregation. // Returned response contains a results property containing the requested aggregation.
this.getMetricData = function ( function getMetricData(
index, index,
types, types,
entityFields, entityFields,
@ -1364,7 +1362,7 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain event rate data i.e. the count // Queries Elasticsearch to obtain event rate data i.e. the count
// of documents over time. // of documents over time.
@ -1372,7 +1370,7 @@ module.service('mlResultsService', function ($q, es, ml) {
// Extra query object can be supplied, or pass null if no additional query. // Extra query object can be supplied, or pass null if no additional query.
// Returned response contains a results property, which is an object // Returned response contains a results property, which is an object
// of document counts against time (epoch millis). // of document counts against time (epoch millis).
this.getEventRateData = function ( function getEventRateData(
index, index,
query, query,
timeFieldName, timeFieldName,
@ -1440,9 +1438,9 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
this.getModelPlotOutput = function ( function getModelPlotOutput(
jobId, jobId,
detectorIndex, detectorIndex,
criteriaFields, criteriaFields,
@ -1584,13 +1582,13 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}; }
// Queries Elasticsearch to obtain the max record score over time for the specified job, // Queries Elasticsearch to obtain the max record score over time for the specified job,
// criteria, time range, and aggregation interval. // criteria, time range, and aggregation interval.
// criteriaFields parameter must be an array, with each object in the array having 'fieldName' // criteriaFields parameter must be an array, with each object in the array having 'fieldName'
// 'fieldValue' properties. // 'fieldValue' properties.
this.getRecordMaxScoreByTime = function (jobId, criteriaFields, earliestMs, latestMs, interval) { function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, latestMs, interval) {
return $q((resolve, reject) => { return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
@ -1698,6 +1696,26 @@ module.service('mlResultsService', function ($q, es, ml) {
reject(resp); reject(resp);
}); });
}); });
}
return {
getScoresByBucket,
getScheduledEventsByBucket,
getTopInfluencers,
getTopInfluencerValues,
getOverallBucketScores,
getInfluencerValueMaxScoreByTime,
getCategoryDefinition,
getCategoryExamples,
getRecordInfluencers,
getRecordsForInfluencer,
getRecordsForDetector,
getRecords,
getRecordsForCriteria,
getMetricData,
getEventRateData,
getModelPlotOutput,
getRecordMaxScoreByTime
}; };
}); }

View file

@ -17,6 +17,7 @@ import { checkLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege'; import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege';
import { getMlNodeCount, mlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { getMlNodeCount, mlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { buttonsEnabledChecks } from 'plugins/ml/settings/scheduled_events/calendars_list/buttons_enabled_checks'; import { buttonsEnabledChecks } from 'plugins/ml/settings/scheduled_events/calendars_list/buttons_enabled_checks';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './calendars_list.html'; import template from './calendars_list.html';
@ -39,9 +40,9 @@ module.controller('MlCalendarsList',
$filter, $filter,
$route, $route,
$location, $location,
$q,
pagerFactory, pagerFactory,
Private, Private,
ml,
timefilter, timefilter,
mlConfirmModalService) { mlConfirmModalService) {
@ -116,7 +117,7 @@ module.controller('MlCalendarsList',
title: `Delete calendar` title: `Delete calendar`
}) })
.then(() => { .then(() => {
ml.deleteCalendar({ calendarId }) $q.when(ml.deleteCalendar({ calendarId }))
.then(loadCalendars) .then(loadCalendars)
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
@ -126,7 +127,7 @@ module.controller('MlCalendarsList',
}; };
function loadCalendars() { function loadCalendars() {
ml.calendars() $q.when(ml.calendars())
.then((resp) => { .then((resp) => {
calendars = resp; calendars = resp;
$scope.pager = pagerFactory.create(calendars.length, PAGE_SIZE, 1); $scope.pager = pagerFactory.create(calendars.length, PAGE_SIZE, 1);

View file

@ -18,6 +18,9 @@ import uiRoutes from 'ui/routes';
import { checkLicense } from 'plugins/ml/license/check_license'; import { checkLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { CalendarServiceProvider } from 'plugins/ml/services/calendar_service';
import { ml } from 'plugins/ml/services/ml_api_service';
import template from './create_calendar.html'; import template from './create_calendar.html';
@ -47,13 +50,14 @@ module.controller('MlCreateCalendar',
$scope, $scope,
$route, $route,
$location, $location,
ml, $q,
timefilter, timefilter,
mlMessageBarService, mlMessageBarService,
mlJobService, Private) {
mlCalendarService) {
const msgs = mlMessageBarService; const msgs = mlMessageBarService;
msgs.clear(); msgs.clear();
const mlJobService = Private(JobServiceProvider);
const mlCalendarService = Private(CalendarServiceProvider);
const calendarId = $route.current.params.calendarId; const calendarId = $route.current.params.calendarId;
$scope.isNewCalendar = (calendarId === undefined); $scope.isNewCalendar = (calendarId === undefined);
@ -137,8 +141,8 @@ module.controller('MlCreateCalendar',
if (validateCalendarId(calendar.calendarId, $scope.validation.checks)) { if (validateCalendarId(calendar.calendarId, $scope.validation.checks)) {
$scope.saveLock = true; $scope.saveLock = true;
const saveFunc = $scope.isNewCalendar ? ml.addCalendar : ml.updateCalendar; const saveFunc = $scope.isNewCalendar ? (c => ml.addCalendar(c)) : (c => ml.updateCalendar(c));
saveFunc(calendar) $q.when(saveFunc(calendar))
.then(() => { .then(() => {
$location.path('settings/calendars_list'); $location.path('settings/calendars_list');
$scope.saveLock = false; $scope.saveLock = false;

View file

@ -32,6 +32,7 @@ import { isJobVersionGte } from '../../../common/util/job_utils';
import { parseInterval } from '../../../common/util/parse_interval'; import { parseInterval } from '../../../common/util/parse_interval';
import { Modal } from './modal'; import { Modal } from './modal';
import { PROGRESS_STATES } from './progress_states'; import { PROGRESS_STATES } from './progress_states';
import { ml } from 'plugins/ml/services/ml_api_service';
const FORECAST_JOB_MIN_VERSION = '6.1.0'; // Forecasting only allowed for jobs created >= 6.1.0. const FORECAST_JOB_MIN_VERSION = '6.1.0'; // Forecasting only allowed for jobs created >= 6.1.0.
const FORECASTS_VIEW_MAX = 5; // Display links to a maximum of 5 forecasts. const FORECASTS_VIEW_MAX = 5; // Display links to a maximum of 5 forecasts.
@ -122,7 +123,7 @@ class ForecastingModal extends Component {
jobOpeningState: PROGRESS_STATES.WAITING jobOpeningState: PROGRESS_STATES.WAITING
}); });
this.props.jobService.openJob(this.props.job.job_id) this.props.mlJobService.openJob(this.props.job.job_id)
.then(() => { .then(() => {
// If open was successful run the forecast, then close the job again. // If open was successful run the forecast, then close the job again.
this.setState({ this.setState({
@ -159,7 +160,7 @@ class ForecastingModal extends Component {
// formats accepted by Kibana (w, M, y) are not valid formats in Elasticsearch. // formats accepted by Kibana (w, M, y) are not valid formats in Elasticsearch.
const durationInSeconds = parseInterval(this.state.newForecastDuration).asSeconds(); const durationInSeconds = parseInterval(this.state.newForecastDuration).asSeconds();
this.props.forecastService.runForecast(this.props.job.job_id, `${durationInSeconds}s`) this.props.mlForecastService.runForecast(this.props.job.job_id, `${durationInSeconds}s`)
.then((resp) => { .then((resp) => {
// Endpoint will return { acknowledged:true, id: <now timestamp> } before forecast is complete. // Endpoint will return { acknowledged:true, id: <now timestamp> } before forecast is complete.
// So wait for results and then refresh the dashboard to the end of the forecast. // So wait for results and then refresh the dashboard to the end of the forecast.
@ -179,7 +180,7 @@ class ForecastingModal extends Component {
let previousProgress = 0; let previousProgress = 0;
let noProgressMs = 0; let noProgressMs = 0;
this.forecastChecker = setInterval(() => { this.forecastChecker = setInterval(() => {
this.props.forecastService.getForecastRequestStats(this.props.job, forecastId) this.props.mlForecastService.getForecastRequestStats(this.props.job, forecastId)
.then((resp) => { .then((resp) => {
// Get the progress (stats value is between 0 and 1). // Get the progress (stats value is between 0 and 1).
const progress = _.get(resp, ['stats', 'forecast_progress'], previousProgress); const progress = _.get(resp, ['stats', 'forecast_progress'], previousProgress);
@ -197,7 +198,7 @@ class ForecastingModal extends Component {
if (closeJobAfterRunning === true) { if (closeJobAfterRunning === true) {
this.setState({ jobClosingState: PROGRESS_STATES.WAITING }); this.setState({ jobClosingState: PROGRESS_STATES.WAITING });
this.props.jobService.closeJob(this.props.job.job_id) this.props.mlJobService.closeJob(this.props.job.job_id)
.then(() => { .then(() => {
this.setState({ this.setState({
jobClosingState: PROGRESS_STATES.DONE jobClosingState: PROGRESS_STATES.DONE
@ -262,7 +263,7 @@ class ForecastingModal extends Component {
forecast_status: FORECAST_REQUEST_STATE.FINISHED forecast_status: FORECAST_REQUEST_STATE.FINISHED
} }
}; };
this.props.forecastService.getForecastsSummary( this.props.mlForecastService.getForecastsSummary(
job, job,
statusFinishedQuery, statusFinishedQuery,
bounds.min.valueOf(), bounds.min.valueOf(),
@ -281,14 +282,15 @@ class ForecastingModal extends Component {
// of partitioning fields. // of partitioning fields.
const entityFieldNames = this.props.entities.map(entity => entity.fieldName); const entityFieldNames = this.props.entities.map(entity => entity.fieldName);
if (entityFieldNames.length > 0) { if (entityFieldNames.length > 0) {
this.props.fieldsService.getCardinalityOfFields( ml.getCardinalityOfFields({
job.datafeed_config.indices, index: job.datafeed_config.indices,
job.datafeed_config.types, types: job.datafeed_config.types,
entityFieldNames, fieldNames: entityFieldNames,
job.datafeed_config.query, query: job.datafeed_config.query,
job.data_description.time_field, timeFieldName: job.data_description.time_field,
job.data_counts.earliest_record_timestamp, earliestMs: job.data_counts.earliest_record_timestamp,
job.data_counts.latest_record_timestamp) latestMs: job.data_counts.latest_record_timestamp
})
.then((results) => { .then((results) => {
let numPartitions = 1; let numPartitions = 1;
Object.values(results).forEach((cardinality) => { Object.values(results).forEach((cardinality) => {
@ -397,9 +399,8 @@ ForecastingModal.propTypes = {
job: PropTypes.object, job: PropTypes.object,
detectorIndex: PropTypes.number, detectorIndex: PropTypes.number,
entities: PropTypes.array, entities: PropTypes.array,
forecastService: PropTypes.object.isRequired, mlForecastService: PropTypes.object.isRequired,
jobService: PropTypes.object.isRequired, mlJobService: PropTypes.object.isRequired,
fieldsService: PropTypes.object.isRequired,
loadForForecastId: PropTypes.func, loadForForecastId: PropTypes.func,
}; };

View file

@ -10,24 +10,23 @@ import 'ngreact';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']); const module = uiModules.get('apps/ml', ['react']);
import { FieldsServiceProvider } from 'plugins/ml/services/fields_service'; import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { ForecastServiceProvider } from 'plugins/ml/services/forecast_service';
import { ForecastingModal } from './forecasting_modal'; import { ForecastingModal } from './forecasting_modal';
module.directive('mlForecastingModal', function ($injector, Private) { module.directive('mlForecastingModal', function ($injector) {
const forecastService = $injector.get('mlForecastService'); const Private = $injector.get('Private');
const jobService = $injector.get('mlJobService'); const mlJobService = Private(JobServiceProvider);
const fieldsService = Private(FieldsServiceProvider); const mlForecastService = Private(ForecastServiceProvider);
const timefilter = $injector.get('timefilter'); const timefilter = $injector.get('timefilter');
const reactDirective = $injector.get('reactDirective'); const reactDirective = $injector.get('reactDirective');
return reactDirective( return reactDirective(
ForecastingModal, ForecastingModal,
undefined, undefined,
{ restrict: 'E' }, { restrict: 'E' },
{ {
forecastService, mlForecastService,
jobService, mlJobService,
fieldsService,
timefilter timefilter
} }
); );

View file

@ -31,6 +31,7 @@ import ContextChartMask from 'plugins/ml/timeseriesexplorer/context_chart_mask';
import { findNearestChartPointToTime } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils'; import { findNearestChartPointToTime } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
import 'plugins/ml/filters/format_value'; import 'plugins/ml/filters/format_value';
import { mlEscape } from 'plugins/ml/util/string_utils'; import { mlEscape } from 'plugins/ml/util/string_utils';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
@ -38,15 +39,15 @@ const module = uiModules.get('apps/ml');
module.directive('mlTimeseriesChart', function ( module.directive('mlTimeseriesChart', function (
$compile, $compile,
$timeout, $timeout,
Private,
timefilter, timefilter,
mlAnomaliesTableService, mlAnomaliesTableService,
mlFieldFormatService,
formatValueFilter, formatValueFilter,
Private,
mlChartTooltipService) { mlChartTooltipService) {
function link(scope, element) { function link(scope, element) {
const mlFieldFormatService = Private(FieldFormatServiceProvider);
// Key dimensions for the viz and constituent charts. // Key dimensions for the viz and constituent charts.
let svgWidth = angular.element('.results-container').width(); let svgWidth = angular.element('.results-container').width();
const focusZoomPanelHeight = 25; const focusZoomPanelHeight = 25;

View file

@ -8,19 +8,19 @@
import _ from 'lodash'; import _ from 'lodash';
import { FieldsServiceProvider } from 'plugins/ml/services/fields_service'; import { ml } from 'plugins/ml/services/ml_api_service';
import { isModelPlotEnabled } from 'plugins/ml/../common/util/job_utils'; import { isModelPlotEnabled } from 'plugins/ml/../common/util/job_utils';
import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder'; import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import { uiModules } from 'ui/modules'; import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml'); const module = uiModules.get('apps/ml');
module.service('mlTimeSeriesSearchService', function ( module.service('mlTimeSeriesSearchService', function (
$q, $q,
$timeout, Private) {
Private,
es, const mlResultsService = Private(ResultsServiceProvider);
mlResultsService) {
this.getMetricData = function (job, detectorIndex, entityFields, earliestMs, latestMs, interval) { this.getMetricData = function (job, detectorIndex, entityFields, earliestMs, latestMs, interval) {
if (isModelPlotEnabled(job, detectorIndex, entityFields)) { if (isModelPlotEnabled(job, detectorIndex, entityFields)) {
@ -63,7 +63,7 @@ module.service('mlTimeSeriesSearchService', function (
interval interval
); );
} else { } else {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { const obj = {
success: true, success: true,
results: {} results: {}
@ -90,13 +90,13 @@ module.service('mlTimeSeriesSearchService', function (
}; };
}); });
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
return deferred.promise; });
} }
}; };
@ -106,7 +106,7 @@ module.service('mlTimeSeriesSearchService', function (
// Queries Elasticsearch if necessary to obtain the distinct count of entities // Queries Elasticsearch if necessary to obtain the distinct count of entities
// for which data is being plotted. // for which data is being plotted.
this.getChartDetails = function (job, detectorIndex, entityFields, earliestMs, latestMs) { this.getChartDetails = function (job, detectorIndex, entityFields, earliestMs, latestMs) {
const deferred = $q.defer(); return $q((resolve, reject) => {
const obj = { success: true, results: { functionLabel: '', entityData: { entities: [] } } }; const obj = { success: true, results: { functionLabel: '', entityData: { entities: [] } } };
const chartConfig = buildConfigFromDetector(job, detectorIndex); const chartConfig = buildConfigFromDetector(job, detectorIndex);
@ -126,18 +126,18 @@ module.service('mlTimeSeriesSearchService', function (
if (blankEntityFields.length === 0) { if (blankEntityFields.length === 0) {
obj.results.entityData.count = 1; obj.results.entityData.count = 1;
obj.results.entityData.entities = entityFields; obj.results.entityData.entities = entityFields;
deferred.resolve(obj); resolve(obj);
} else { } else {
const entityFieldNames = _.map(blankEntityFields, 'fieldName'); const entityFieldNames = _.map(blankEntityFields, 'fieldName');
const fieldsService = Private(FieldsServiceProvider); ml.getCardinalityOfFields({
fieldsService.getCardinalityOfFields( index: chartConfig.datafeedConfig.indices,
chartConfig.datafeedConfig.indices, types: chartConfig.datafeedConfig.types,
chartConfig.datafeedConfig.types, fieldNames: entityFieldNames,
entityFieldNames, query: chartConfig.datafeedConfig.query,
chartConfig.datafeedConfig.query, timeFieldName: chartConfig.timeField,
chartConfig.timeField,
earliestMs, earliestMs,
latestMs) latestMs
})
.then((results) => { .then((results) => {
_.each(blankEntityFields, (field) => { _.each(blankEntityFields, (field) => {
obj.results.entityData.entities.push({ obj.results.entityData.entities.push({
@ -146,14 +146,14 @@ module.service('mlTimeSeriesSearchService', function (
}); });
}); });
deferred.resolve(obj); resolve(obj);
}) })
.catch((resp) => { .catch((resp) => {
deferred.reject(resp); reject(resp);
}); });
} }
return deferred.promise; });
}; };
}); });

View file

@ -16,10 +16,6 @@ import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import 'plugins/ml/components/anomalies_table'; import 'plugins/ml/components/anomalies_table';
import 'plugins/ml/services/field_format_service';
import 'plugins/ml/services/forecast_service';
import 'plugins/ml/services/job_service';
import 'plugins/ml/services/results_service';
import { notify } from 'ui/notify'; import { notify } from 'ui/notify';
import uiRoutes from 'ui/routes'; import uiRoutes from 'ui/routes';
@ -42,8 +38,12 @@ import {
processScheduledEventsForChart } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils'; processScheduledEventsForChart } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher'; import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher';
import { IntervalHelperProvider, getBoundsRoundedToInterval } from 'plugins/ml/util/ml_time_buckets'; import { IntervalHelperProvider, getBoundsRoundedToInterval } from 'plugins/ml/util/ml_time_buckets';
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
import template from './timeseriesexplorer.html'; import template from './timeseriesexplorer.html';
import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { JobServiceProvider } from 'plugins/ml/services/job_service';
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
import { ForecastServiceProvider } from 'plugins/ml/services/forecast_service';
uiRoutes uiRoutes
.when('/timeseriesexplorer/?', { .when('/timeseriesexplorer/?', {
@ -63,20 +63,12 @@ module.controller('MlTimeSeriesExplorerController', function (
$scope, $scope,
$route, $route,
$timeout, $timeout,
$compile,
$modal,
Private, Private,
$q,
es,
timefilter, timefilter,
AppState, AppState,
mlJobService,
mlResultsService,
mlJobSelectService, mlJobSelectService,
mlTimeSeriesSearchService, mlTimeSeriesSearchService,
mlForecastService, mlAnomaliesTableService) {
mlAnomaliesTableService,
mlFieldFormatService) {
$scope.timeFieldName = 'timestamp'; $scope.timeFieldName = 'timestamp';
timefilter.enableTimeRangeSelector(); timefilter.enableTimeRangeSelector();
@ -86,6 +78,10 @@ module.controller('MlTimeSeriesExplorerController', function (
const ANOMALIES_MAX_RESULTS = 500; const ANOMALIES_MAX_RESULTS = 500;
const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket. const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket.
const TimeBuckets = Private(IntervalHelperProvider); const TimeBuckets = Private(IntervalHelperProvider);
const mlResultsService = Private(ResultsServiceProvider);
const mlJobService = Private(JobServiceProvider);
const mlFieldFormatService = Private(FieldFormatServiceProvider);
const mlForecastService = Private(ForecastServiceProvider);
$scope.jobPickerSelections = []; $scope.jobPickerSelections = [];
$scope.selectedJob; $scope.selectedJob;

View file

@ -0,0 +1,44 @@
/*
* 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.
*/
// service for copying text to the users clipboard
// can only work when triggered via a user event, as part of an onclick or ng-click
// returns success
// e.g. mlClipboardService.copy("this could be abused!");
export function copyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = 0;
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
let successful = false;
try {
successful = document.execCommand('copy');
const msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
return successful;
}