mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Converting services to be non-angular imports (#18687)
* [ML] Removing angular from services * removing commented out code * fix include path for tests * adding flag to initPromise * moving job service for jest tests to pass * moving initPromise to its own file
This commit is contained in:
parent
a4cfc25e46
commit
d52729b862
70 changed files with 4148 additions and 4251 deletions
|
@ -21,6 +21,7 @@ import { formatValue } from 'plugins/ml/formatters/format_value';
|
|||
import { getUrlForRecord } from 'plugins/ml/util/custom_url_utils';
|
||||
import { replaceStringTokens, mlEscape } from 'plugins/ml/util/string_utils';
|
||||
import { isTimeSeriesViewDetector } from 'plugins/ml/../common/util/job_utils';
|
||||
import { getIndexPatternIdFromName } from 'plugins/ml/util/index_utils';
|
||||
import {
|
||||
getEntityFieldName,
|
||||
getEntityFieldValue,
|
||||
|
@ -29,9 +30,9 @@ import {
|
|||
getSeverity
|
||||
} 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 { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import template from './anomalies_table.html';
|
||||
|
||||
import 'plugins/ml/components/controls';
|
||||
|
@ -75,9 +76,6 @@ module.directive('mlAnomaliesTable', function (
|
|||
// just remove these resets.
|
||||
mlSelectIntervalService.state.reset().changed();
|
||||
mlSelectSeverityService.state.reset().changed();
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlFieldFormatService = Private(FieldFormatServiceProvider);
|
||||
|
||||
scope.momentInterval = 'second';
|
||||
|
||||
|
@ -220,14 +218,7 @@ module.directive('mlAnomaliesTable', function (
|
|||
// index configured in the datafeed. If a Kibana index pattern has not been created
|
||||
// for this index, then the user will see a warning message on the Discover tab advising
|
||||
// them that no matching index pattern has been configured.
|
||||
const indexPatterns = $route.current.locals.indexPatterns;
|
||||
let indexPatternId = index;
|
||||
for (let j = 0; j < indexPatterns.length; j++) {
|
||||
if (indexPatterns[j].get('title') === index) {
|
||||
indexPatternId = indexPatterns[j].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const indexPatternId = getIndexPatternIdFromName(index);
|
||||
|
||||
// Get the definition of the category and use the terms or regex to view the
|
||||
// matching events in the Kibana Discover tab depending on whether the
|
||||
|
|
|
@ -13,10 +13,10 @@ import template from './confirm_modal.html';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlConfirmModalService', function ($modal, $q) {
|
||||
module.service('mlConfirmModalService', function ($modal) {
|
||||
|
||||
this.open = function (options) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$modal.open({
|
||||
template,
|
||||
controller: 'MlConfirmModal',
|
||||
|
|
|
@ -13,59 +13,54 @@ import { RecognizedResult } from './recognized_result';
|
|||
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function dataRecognizerProvider() {
|
||||
export class DataRecognizer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
class DataRecognizer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
results: []
|
||||
};
|
||||
|
||||
this.state = {
|
||||
results: []
|
||||
};
|
||||
|
||||
this.indexPattern = props.indexPattern;
|
||||
this.savedSearch = props.savedSearch;
|
||||
this.className = props.className;
|
||||
this.results = props.results;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// once the mount is complete, call the recognize endpoint to see if the index format is known to us,
|
||||
ml.recognizeIndex({ indexPatternTitle: this.indexPattern.title })
|
||||
.then((resp) => {
|
||||
const results = resp.map((r) => (
|
||||
<RecognizedResult
|
||||
key={r.id}
|
||||
config={r}
|
||||
indexPattern={this.indexPattern}
|
||||
savedSearch={this.savedSearch}
|
||||
/>
|
||||
));
|
||||
if (typeof this.results === 'object') {
|
||||
this.results.count = results.length;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
results
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.className}>
|
||||
{this.state.results}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
this.indexPattern = props.indexPattern;
|
||||
this.savedSearch = props.savedSearch;
|
||||
this.className = props.className;
|
||||
this.results = props.results;
|
||||
}
|
||||
|
||||
DataRecognizer.propTypes = {
|
||||
indexPattern: PropTypes.object,
|
||||
savedSearch: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
results: PropTypes.object,
|
||||
};
|
||||
componentDidMount() {
|
||||
// once the mount is complete, call the recognize endpoint to see if the index format is known to us,
|
||||
ml.recognizeIndex({ indexPatternTitle: this.indexPattern.title })
|
||||
.then((resp) => {
|
||||
const results = resp.map((r) => (
|
||||
<RecognizedResult
|
||||
key={r.id}
|
||||
config={r}
|
||||
indexPattern={this.indexPattern}
|
||||
savedSearch={this.savedSearch}
|
||||
/>
|
||||
));
|
||||
if (typeof this.results === 'object') {
|
||||
this.results.count = results.length;
|
||||
}
|
||||
|
||||
return DataRecognizer;
|
||||
this.setState({
|
||||
results
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.className}>
|
||||
{this.state.results}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataRecognizer.propTypes = {
|
||||
indexPattern: PropTypes.object,
|
||||
savedSearch: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
results: PropTypes.object,
|
||||
};
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
|
||||
|
||||
import 'ngreact';
|
||||
import { dataRecognizerProvider } from './data_recognizer';
|
||||
import { DataRecognizer } from './data_recognizer';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['react']);
|
||||
module.directive('mlDataRecognizer', function (reactDirective, Private) {
|
||||
const DataRecognizer = Private(dataRecognizerProvider);
|
||||
module.directive('mlDataRecognizer', function (reactDirective) {
|
||||
return reactDirective(DataRecognizer, undefined, { restrict: 'AE' });
|
||||
});
|
||||
|
|
|
@ -11,16 +11,16 @@ import moment from 'moment';
|
|||
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function FullTimeRangeSelectorServiceProvider(timefilter, Notifier, $q) {
|
||||
export function FullTimeRangeSelectorServiceProvider(timefilter, Notifier) {
|
||||
const notify = new Notifier();
|
||||
|
||||
function setFullTimeRange(indexPattern, query) {
|
||||
// load the earliest and latest time stamps for the index
|
||||
$q.when(ml.getTimeFieldRange({
|
||||
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();
|
||||
|
|
|
@ -10,13 +10,13 @@ import _ from 'lodash';
|
|||
|
||||
import template from './job_group_select.html';
|
||||
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { CalendarServiceProvider } from 'plugins/ml/services/calendar_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlCalendarService } from 'plugins/ml/services/calendar_service';
|
||||
import { InitAfterBindingsWorkaround } from 'ui/compat';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlJobGroupSelect', function (es, $timeout, Private) {
|
||||
module.directive('mlJobGroupSelect', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
|
@ -30,8 +30,6 @@ module.directive('mlJobGroupSelect', function (es, $timeout, Private) {
|
|||
controller: class MlGroupSelectController extends InitAfterBindingsWorkaround {
|
||||
|
||||
initAfterBindings($scope) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlCalendarService = Private(CalendarServiceProvider);
|
||||
this.$scope = $scope;
|
||||
this.selectedGroups = [];
|
||||
this.groups = [];
|
||||
|
|
|
@ -18,7 +18,7 @@ import d3 from 'd3';
|
|||
|
||||
import template from './job_select_list.html';
|
||||
import { isTimeSeriesViewJob } from 'plugins/ml/../common/util/job_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
@ -31,7 +31,6 @@ module.directive('mlJobSelectList', function (Private, timefilter) {
|
|||
transclude: true,
|
||||
template,
|
||||
controller: function ($scope) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlJobSelectService = Private(JobSelectServiceProvider);
|
||||
$scope.jobs = [];
|
||||
$scope.groups = [];
|
||||
|
|
|
@ -11,12 +11,11 @@
|
|||
import _ from 'lodash';
|
||||
import { notify } from 'ui/notify';
|
||||
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
let jobSelectService = undefined;
|
||||
|
||||
export function JobSelectServiceProvider($rootScope, Private, globalState) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
export function JobSelectServiceProvider($rootScope, globalState) {
|
||||
|
||||
function checkGlobalState() {
|
||||
if (globalState.ml === undefined) {
|
||||
|
|
|
@ -9,17 +9,13 @@
|
|||
|
||||
import 'ngreact';
|
||||
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['react']);
|
||||
|
||||
import { ValidateJob } from './validate_job_view';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
module.directive('mlValidateJob', function ($injector) {
|
||||
const Private = $injector.get('Private');
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const reactDirective = $injector.get('reactDirective');
|
||||
|
||||
module.directive('mlValidateJob', function (reactDirective) {
|
||||
return reactDirective(
|
||||
ValidateJob,
|
||||
undefined,
|
||||
|
|
|
@ -30,9 +30,10 @@ import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
|
|||
import { checkLicenseExpired } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { createSearchItems } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './datavisualizer.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -41,9 +42,10 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
checkMlNodesAvailable
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -56,7 +58,6 @@ module
|
|||
$route,
|
||||
$timeout,
|
||||
$window,
|
||||
$q,
|
||||
Private,
|
||||
timefilter,
|
||||
AppState) {
|
||||
|
@ -461,7 +462,7 @@ module
|
|||
buckets.setBarTarget(BAR_TARGET);
|
||||
const aggInterval = buckets.getInterval();
|
||||
|
||||
$q.when(ml.getVisualizerFieldStats({
|
||||
ml.getVisualizerFieldStats({
|
||||
indexPatternTitle: indexPattern.title,
|
||||
query: $scope.searchQuery,
|
||||
timeFieldName: indexPattern.timeFieldName,
|
||||
|
@ -470,7 +471,7 @@ module
|
|||
samplerShardSize: $scope.samplerShardSize,
|
||||
interval: aggInterval.expression,
|
||||
fields: numberFields
|
||||
}))
|
||||
})
|
||||
.then((resp) => {
|
||||
// Add the metric stats to the existing stats in the corresponding card.
|
||||
_.each($scope.metricCards, (card) => {
|
||||
|
@ -492,7 +493,7 @@ module
|
|||
.catch((err) => {
|
||||
// TODO - display error in cards saying data could not be loaded.
|
||||
console.log('DataVisualizer - error getting stats for metric cards from elasticsearch:', err);
|
||||
if (err.statusCode === 500) {
|
||||
if (err.status === 500) {
|
||||
notify.error(`Error loading data for metrics in index ${indexPattern.title}. ${err.message}. ` +
|
||||
'The request may have timed out. Try using a smaller sample size or narrowing the time range.',
|
||||
{ lifetime: 30000 });
|
||||
|
@ -520,7 +521,7 @@ module
|
|||
|
||||
if (fields.length > 0) {
|
||||
|
||||
$q.when(ml.getVisualizerFieldStats({
|
||||
ml.getVisualizerFieldStats({
|
||||
indexPatternTitle: indexPattern.title,
|
||||
query: $scope.searchQuery,
|
||||
fields: fields,
|
||||
|
@ -529,7 +530,7 @@ module
|
|||
latest: $scope.latest,
|
||||
samplerShardSize: $scope.samplerShardSize,
|
||||
maxExamples: 10
|
||||
}))
|
||||
})
|
||||
.then((resp) => {
|
||||
// Add the metric stats to the existing stats in the corresponding card.
|
||||
_.each($scope.fieldCards, (card) => {
|
||||
|
@ -543,7 +544,7 @@ module
|
|||
.catch((err) => {
|
||||
// TODO - display error in cards saying data could not be loaded.
|
||||
console.log('DataVisualizer - error getting non metric field stats from elasticsearch:', err);
|
||||
if (err.statusCode === 500) {
|
||||
if (err.status === 500) {
|
||||
notify.error(`Error loading data for fields in index ${indexPattern.title}. ${err.message}. ` +
|
||||
'The request may have timed out. Try using a smaller sample size or narrowing the time range.',
|
||||
{ lifetime: 30000 });
|
||||
|
@ -575,7 +576,7 @@ module
|
|||
// 2. List of aggregatable fields that do not 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.
|
||||
$q.when(ml.getVisualizerOverallStats({
|
||||
ml.getVisualizerOverallStats({
|
||||
indexPatternTitle: indexPattern.title,
|
||||
query: $scope.searchQuery,
|
||||
timeFieldName: indexPattern.timeFieldName,
|
||||
|
@ -584,7 +585,7 @@ module
|
|||
latest: $scope.latest,
|
||||
aggregatableFields: aggregatableFields,
|
||||
nonAggregatableFields: nonAggregatableFields
|
||||
}))
|
||||
})
|
||||
.then((resp) => {
|
||||
$scope.overallStats = resp;
|
||||
createMetricCards();
|
||||
|
@ -593,7 +594,7 @@ module
|
|||
.catch((err) => {
|
||||
// TODO - display error in cards saying data could not be loaded.
|
||||
console.log('DataVisualizer - error getting overall stats from elasticsearch:', err);
|
||||
if (err.statusCode === 500) {
|
||||
if (err.status === 500) {
|
||||
notify.error(`Error loading data for fields in index ${indexPattern.title}. ${err.message}. ` +
|
||||
'The request may have timed out. Try using a smaller sample size or narrowing the time range.',
|
||||
{ lifetime: 30000 });
|
||||
|
|
|
@ -16,10 +16,9 @@ import _ from 'lodash';
|
|||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder';
|
||||
import { mlEscape } from 'plugins/ml/util/string_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
export function explorerChartConfigBuilder(Private) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
export function explorerChartConfigBuilder() {
|
||||
|
||||
const compiledTooltip = _.template(
|
||||
'<div class="explorer-chart-info-tooltip">job ID: <%= jobId %><br/>' +
|
||||
|
|
|
@ -24,7 +24,7 @@ import { drawLineChartDots, numTicksForDateFormat } from 'plugins/ml/util/chart_
|
|||
import { TimeBuckets } from 'ui/time_buckets';
|
||||
import loadingIndicatorWrapperTemplate from 'plugins/ml/components/loading_indicator/loading_indicator_wrapper.html';
|
||||
import { mlEscape } from 'plugins/ml/util/string_utils';
|
||||
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
@ -35,7 +35,6 @@ module.directive('mlExplorerChart', function (
|
|||
mlSelectSeverityService) {
|
||||
|
||||
function link(scope, element) {
|
||||
const mlFieldFormatService = Private(FieldFormatServiceProvider);
|
||||
console.log('ml-explorer-chart directive link series config:', scope.seriesConfig);
|
||||
if (typeof scope.seriesConfig === 'undefined') {
|
||||
// just return so the empty directive renders without an error later on
|
||||
|
|
|
@ -21,16 +21,13 @@ const module = uiModules.get('apps/ml');
|
|||
import { explorerChartConfigBuilder } from './explorer_chart_config_builder';
|
||||
import { chartLimits } from 'plugins/ml/util/chart_utils';
|
||||
import { isTimeSeriesViewDetector } from 'plugins/ml/../common/util/job_utils';
|
||||
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
module.controller('MlExplorerChartsContainerController', function ($scope, $injector) {
|
||||
const Private = $injector.get('Private');
|
||||
const mlExplorerDashboardService = $injector.get('mlExplorerDashboardService');
|
||||
const mlSelectSeverityService = $injector.get('mlSelectSeverityService');
|
||||
const $q = $injector.get('$q');
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
|
||||
$scope.seriesToPlot = [];
|
||||
|
||||
|
@ -124,7 +121,7 @@ module.controller('MlExplorerChartsContainerController', function ($scope, $inje
|
|||
// only after that trigger data processing and page render.
|
||||
// TODO - if query returns no results e.g. source data has been deleted,
|
||||
// display a message saying 'No data between earliest/latest'.
|
||||
const seriesPromises = seriesConfigs.map(seriesConfig => $q.all([
|
||||
const seriesPromises = seriesConfigs.map(seriesConfig => Promise.all([
|
||||
getMetricData(seriesConfig, chartRange),
|
||||
getRecordsForCriteria(seriesConfig, chartRange),
|
||||
getScheduledEvents(seriesConfig, chartRange)
|
||||
|
@ -243,7 +240,7 @@ module.controller('MlExplorerChartsContainerController', function ($scope, $inje
|
|||
return chartPoint;
|
||||
}
|
||||
|
||||
$q.all(seriesPromises)
|
||||
Promise.all(seriesPromises)
|
||||
.then(response => {
|
||||
// calculate an overall min/max for all series
|
||||
const processedData = response.map(processChartData);
|
||||
|
|
|
@ -23,17 +23,18 @@ import 'plugins/ml/components/job_select_list';
|
|||
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './explorer.html';
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { checkLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import { loadIndexPatterns, getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher';
|
||||
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';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
|
||||
|
||||
uiRoutes
|
||||
|
@ -42,7 +43,8 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
indexPatterns: getIndexPatterns
|
||||
indexPatterns: loadIndexPatterns,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -51,7 +53,6 @@ const module = uiModules.get('apps/ml');
|
|||
|
||||
module.controller('MlExplorerController', function (
|
||||
$scope,
|
||||
$route,
|
||||
$timeout,
|
||||
AppState,
|
||||
Private,
|
||||
|
@ -69,9 +70,6 @@ module.controller('MlExplorerController', function (
|
|||
|
||||
const TimeBuckets = Private(IntervalHelperProvider);
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlFieldFormatService = Private(FieldFormatServiceProvider);
|
||||
const mlJobSelectService = Private(JobSelectServiceProvider);
|
||||
|
||||
let resizeTimeout = null;
|
||||
|
@ -213,7 +211,7 @@ module.controller('MlExplorerController', function (
|
|||
$scope.appState.save();
|
||||
|
||||
// Populate the map of jobs / detectors / field formatters for the selected IDs.
|
||||
mlFieldFormatService.populateFormats(selectedIds, $route.current.locals.indexPatterns)
|
||||
mlFieldFormatService.populateFormats(selectedIds, getIndexPatterns())
|
||||
.finally(() => {
|
||||
// Load the data - if the FieldFormats failed to populate
|
||||
// the default formatting will be used for metric values.
|
||||
|
|
|
@ -33,7 +33,7 @@ module.directive('mlCustomUrlEditor', function (Private) {
|
|||
replace: true,
|
||||
transclude: true,
|
||||
template,
|
||||
controller: function ($scope, $q) {
|
||||
controller: function ($scope) {
|
||||
const URL_TYPE = {
|
||||
KIBANA_DASHBOARD: 'KIBANA_DASHBOARD',
|
||||
KIBANA_DISCOVER: 'KIBANA_DISCOVER',
|
||||
|
@ -88,7 +88,7 @@ module.directive('mlCustomUrlEditor', function (Private) {
|
|||
});
|
||||
}
|
||||
|
||||
$q.all([
|
||||
Promise.all([
|
||||
getSavedDashboards(),
|
||||
getSavedIndexPatterns()
|
||||
])
|
||||
|
|
|
@ -12,103 +12,102 @@ import { parseInterval } from 'ui/utils/parse_interval';
|
|||
|
||||
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
|
||||
import { replaceTokensInUrlValue } from 'plugins/ml/util/custom_url_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
|
||||
export function CustomUrlEditorServiceProvider(es, Private, $q) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
|
||||
// Builds the full URL for testing out a custom URL configuration, which
|
||||
// may contain dollar delimited partition / influencer entity tokens and
|
||||
// drilldown time range settings.
|
||||
function getTestUrl(job, urlConfig) {
|
||||
const urlValue = urlConfig.url_value;
|
||||
const bucketSpanSecs = parseInterval(job.analysis_config.bucket_span).asSeconds();
|
||||
|
||||
// By default, return configured url_value. Look to substitute any dollar-delimited
|
||||
// tokens with values from the highest scoring anomaly, or if no anomalies, with
|
||||
// values from a document returned by the search in the job datafeed.
|
||||
let testUrl = urlConfig.url_value;
|
||||
|
||||
// Query to look for the highest scoring anomaly.
|
||||
const body = {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { job_id: job.job_id } },
|
||||
{ term: { result_type: 'record' } }
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1,
|
||||
_source: {
|
||||
excludes: []
|
||||
},
|
||||
sort: [
|
||||
{ record_score: { order: 'desc' } }
|
||||
]
|
||||
};
|
||||
|
||||
return $q((resolve, reject) => {
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
body
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total > 0) {
|
||||
const record = resp.hits.hits[0]._source;
|
||||
testUrl = replaceTokensInUrlValue(urlConfig, bucketSpanSecs, record, 'timestamp');
|
||||
resolve(testUrl);
|
||||
} else {
|
||||
// No anomalies yet for this job, so do a preview of the search
|
||||
// configured in the job datafeed to obtain sample docs.
|
||||
mlJobService.searchPreview(job)
|
||||
.then((response) => {
|
||||
let testDoc;
|
||||
const docTimeFieldName = job.data_description.time_field;
|
||||
|
||||
// Handle datafeeds which use aggregations or documents.
|
||||
if (response.aggregations) {
|
||||
// Create a dummy object which contains the fields necessary to build the URL.
|
||||
const firstBucket = response.aggregations.buckets.buckets[0];
|
||||
testDoc = {
|
||||
[docTimeFieldName]: firstBucket.key
|
||||
};
|
||||
|
||||
// Look for bucket aggregations which match the tokens in the URL.
|
||||
urlValue.replace((/\$([^?&$\'"]{1,40})\$/g), (match, name) => {
|
||||
if (name !== 'earliest' && name !== 'latest' && firstBucket[name] !== undefined) {
|
||||
const tokenBuckets = firstBucket[name];
|
||||
if (tokenBuckets.buckets) {
|
||||
testDoc[name] = tokenBuckets.buckets[0].key;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (response.hits.total > 0) {
|
||||
testDoc = response.hits.hits[0]._source;
|
||||
}
|
||||
}
|
||||
|
||||
if (testDoc !== undefined) {
|
||||
testUrl = replaceTokensInUrlValue(urlConfig, bucketSpanSecs, testDoc, docTimeFieldName);
|
||||
}
|
||||
|
||||
resolve(testUrl);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
|
||||
return {
|
||||
getTestUrl
|
||||
// Builds the full URL for testing out a custom URL configuration, which
|
||||
// may contain dollar delimited partition / influencer entity tokens and
|
||||
// drilldown time range settings.
|
||||
function getTestUrl(job, urlConfig) {
|
||||
const urlValue = urlConfig.url_value;
|
||||
const bucketSpanSecs = parseInterval(job.analysis_config.bucket_span).asSeconds();
|
||||
|
||||
// By default, return configured url_value. Look to substitute any dollar-delimited
|
||||
// tokens with values from the highest scoring anomaly, or if no anomalies, with
|
||||
// values from a document returned by the search in the job datafeed.
|
||||
let testUrl = urlConfig.url_value;
|
||||
|
||||
// Query to look for the highest scoring anomaly.
|
||||
const body = {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { job_id: job.job_id } },
|
||||
{ term: { result_type: 'record' } }
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1,
|
||||
_source: {
|
||||
excludes: []
|
||||
},
|
||||
sort: [
|
||||
{ record_score: { order: 'desc' } }
|
||||
]
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
body
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total > 0) {
|
||||
const record = resp.hits.hits[0]._source;
|
||||
testUrl = replaceTokensInUrlValue(urlConfig, bucketSpanSecs, record, 'timestamp');
|
||||
resolve(testUrl);
|
||||
} else {
|
||||
// No anomalies yet for this job, so do a preview of the search
|
||||
// configured in the job datafeed to obtain sample docs.
|
||||
mlJobService.searchPreview(job)
|
||||
.then((response) => {
|
||||
let testDoc;
|
||||
const docTimeFieldName = job.data_description.time_field;
|
||||
|
||||
// Handle datafeeds which use aggregations or documents.
|
||||
if (response.aggregations) {
|
||||
// Create a dummy object which contains the fields necessary to build the URL.
|
||||
const firstBucket = response.aggregations.buckets.buckets[0];
|
||||
testDoc = {
|
||||
[docTimeFieldName]: firstBucket.key
|
||||
};
|
||||
|
||||
// Look for bucket aggregations which match the tokens in the URL.
|
||||
urlValue.replace((/\$([^?&$\'"]{1,40})\$/g), (match, name) => {
|
||||
if (name !== 'earliest' && name !== 'latest' && firstBucket[name] !== undefined) {
|
||||
const tokenBuckets = firstBucket[name];
|
||||
if (tokenBuckets.buckets) {
|
||||
testDoc[name] = tokenBuckets.buckets[0].key;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (response.hits.total > 0) {
|
||||
testDoc = response.hits.hits[0]._source;
|
||||
}
|
||||
}
|
||||
|
||||
if (testDoc !== undefined) {
|
||||
testUrl = replaceTokensInUrlValue(urlConfig, bucketSpanSecs, testDoc, docTimeFieldName);
|
||||
}
|
||||
|
||||
resolve(testUrl);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const customUrlEditorService = {
|
||||
getTestUrl
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
|
||||
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { mlCreateWatchService } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { xpackFeatureProvider } from 'plugins/ml/license/check_license';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
@ -13,14 +14,14 @@ const module = uiModules.get('apps/ml');
|
|||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
|
||||
module.controller('MlCreateWatchModal', function ($scope, $modalInstance, params, Private) {
|
||||
const mlCreateWatchService = Private(CreateWatchServiceProvider);
|
||||
const xpackFeature = Private(xpackFeatureProvider);
|
||||
const msgs = mlMessageBarService; // set a reference to the message bar service
|
||||
msgs.clear();
|
||||
|
||||
$scope.jobId = params.job.job_id;
|
||||
$scope.bucketSpan = params.job.analysis_config.bucket_span;
|
||||
|
||||
$scope.watcherEnabled = mlCreateWatchService.isWatcherEnabled();
|
||||
$scope.watcherEnabled = xpackFeature.isAvailable('watcher');
|
||||
$scope.status = mlCreateWatchService.status;
|
||||
$scope.STATUS = mlCreateWatchService.STATUS;
|
||||
|
||||
|
|
|
@ -14,10 +14,10 @@ import numeral from '@elastic/numeral';
|
|||
|
||||
import { calculateDatafeedFrequencyDefaultSeconds } from 'plugins/ml/../common/util/job_utils';
|
||||
import { parseInterval } from 'plugins/ml/../common/util/parse_interval';
|
||||
import { CustomUrlEditorServiceProvider } from 'plugins/ml/jobs/components/custom_url_editor/custom_url_editor_service';
|
||||
import { customUrlEditorService } from 'plugins/ml/jobs/components/custom_url_editor/custom_url_editor_service';
|
||||
import { isWebUrl } from 'plugins/ml/util/string_utils';
|
||||
import { newJobLimits } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
@ -28,11 +28,9 @@ module.controller('MlEditJobModal', function (
|
|||
$modalInstance,
|
||||
$modal,
|
||||
$window,
|
||||
params,
|
||||
Private) {
|
||||
params) {
|
||||
const msgs = mlMessageBarService;
|
||||
msgs.clear();
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
$scope.saveLock = false;
|
||||
const refreshJob = params.pscope.refreshJob;
|
||||
|
||||
|
@ -152,7 +150,6 @@ module.controller('MlEditJobModal', function (
|
|||
};
|
||||
|
||||
$scope.testCustomUrl = function (index) {
|
||||
const customUrlEditorService = Private(CustomUrlEditorServiceProvider);
|
||||
customUrlEditorService.getTestUrl(
|
||||
$scope.job,
|
||||
$scope.job.custom_settings.custom_urls[index])
|
||||
|
|
|
@ -14,7 +14,7 @@ import { copyTextToClipboard } from 'plugins/ml/util/clipboard_utils';
|
|||
import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states';
|
||||
import { ML_DATA_PREVIEW_COUNT } from 'plugins/ml/../common/util/job_utils';
|
||||
import { checkPermission } from 'plugins/ml/privilege/check_privilege';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import numeral from '@elastic/numeral';
|
||||
import chrome from 'ui/chrome';
|
||||
|
@ -24,7 +24,7 @@ import template from './expanded_row.html';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlJobListExpandedRow', function ($location, Private) {
|
||||
module.directive('mlJobListExpandedRow', function ($location) {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
replace: false,
|
||||
|
@ -40,7 +40,6 @@ module.directive('mlJobListExpandedRow', function ($location, Private) {
|
|||
template,
|
||||
link: function ($scope, $element) {
|
||||
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 DATA_FORMAT = '0.0 b';
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import chrome from 'ui/chrome';
|
|||
|
||||
import { FORECAST_REQUEST_STATE } from 'plugins/ml/../common/constants/states';
|
||||
import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed';
|
||||
import { mlForecastService } from 'plugins/ml/services/forecast_service';
|
||||
|
||||
const MAX_FORECASTS = 500;
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
@ -47,7 +48,7 @@ class ForecastsTable extends Component {
|
|||
const dataCounts = this.props.job.data_counts;
|
||||
if (dataCounts.processed_record_count > 0) {
|
||||
// Get the list of all the forecasts with results at or later than the specified 'from' time.
|
||||
this.props.mlForecastService.getForecastsSummary(
|
||||
mlForecastService.getForecastsSummary(
|
||||
this.props.job,
|
||||
null,
|
||||
dataCounts.earliest_record_timestamp,
|
||||
|
@ -247,7 +248,6 @@ class ForecastsTable extends Component {
|
|||
}
|
||||
ForecastsTable.propTypes = {
|
||||
job: PropTypes.object.isRequired,
|
||||
mlForecastService: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export { ForecastsTable };
|
||||
|
|
|
@ -9,22 +9,15 @@
|
|||
|
||||
import 'ngreact';
|
||||
|
||||
import { ForecastServiceProvider } from 'plugins/ml/services/forecast_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['react']);
|
||||
|
||||
import { ForecastsTable } from './forecasts_table';
|
||||
|
||||
module.directive('mlForecastsTable', function ($injector) {
|
||||
const Private = $injector.get('Private');
|
||||
const mlForecastService = Private(ForecastServiceProvider);
|
||||
const reactDirective = $injector.get('reactDirective');
|
||||
|
||||
module.directive('mlForecastsTable', function (reactDirective) {
|
||||
return reactDirective(
|
||||
ForecastsTable,
|
||||
undefined,
|
||||
{ restrict: 'E' },
|
||||
{ mlForecastService }
|
||||
{ restrict: 'E' }
|
||||
);
|
||||
});
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
import moment from 'moment';
|
||||
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 { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { xpackFeatureProvider } from 'plugins/ml/license/check_license';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
@ -23,10 +23,9 @@ module.controller('MlJobTimepickerModal', function (
|
|||
params,
|
||||
Private) {
|
||||
const msgs = mlMessageBarService;
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlCreateWatchService = Private(CreateWatchServiceProvider);
|
||||
$scope.saveLock = false;
|
||||
$scope.watcherEnabled = mlCreateWatchService.isWatcherEnabled();
|
||||
const xpackFeature = Private(xpackFeatureProvider);
|
||||
$scope.watcherEnabled = xpackFeature.isAvailable('watcher');
|
||||
|
||||
const job = angular.copy(params.job);
|
||||
$scope.jobId = job.job_id;
|
||||
|
@ -96,10 +95,10 @@ module.controller('MlJobTimepickerModal', function (
|
|||
doStart();
|
||||
})
|
||||
.catch((resp) => {
|
||||
if (resp.statusCode === 409) {
|
||||
if (resp.status === 409) {
|
||||
doStart();
|
||||
} else {
|
||||
if (resp.statusCode === 500) {
|
||||
if (resp.status === 500) {
|
||||
if (doStartCalled === false) {
|
||||
// doStart hasn't been called yet, this 500 has returned before 10s,
|
||||
// so it's not due to a timeout
|
||||
|
|
|
@ -32,10 +32,11 @@ import createWatchTemplate from 'plugins/ml/jobs/jobs_list/create_watch_modal/cr
|
|||
import { buttonsEnabledChecks } from 'plugins/ml/jobs/jobs_list/buttons_enabled_checks';
|
||||
import { cloudServiceProvider } from 'plugins/ml/services/cloud_service';
|
||||
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';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlCalendarService } from 'plugins/ml/services/calendar_service';
|
||||
import { jobMessagesService } from 'plugins/ml/services/job_messages_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
uiRoutes
|
||||
.when('/jobs/?', {
|
||||
|
@ -44,7 +45,8 @@ uiRoutes
|
|||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
mlNodeCount: getMlNodeCount,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -54,13 +56,10 @@ const module = uiModules.get('apps/ml', ['ui.bootstrap']);
|
|||
module.controller('MlJobsList',
|
||||
function (
|
||||
$scope,
|
||||
$route,
|
||||
$location,
|
||||
$window,
|
||||
$timeout,
|
||||
$compile,
|
||||
$modal,
|
||||
es,
|
||||
timefilter,
|
||||
kbnUrl,
|
||||
Private,
|
||||
|
@ -85,9 +84,6 @@ module.controller('MlJobsList',
|
|||
$scope.mlNodesAvailable = mlNodesAvailable();
|
||||
$scope.permissionToViewMlNodeCount = permissionToViewMlNodeCount();
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlCalendarService = Private(CalendarServiceProvider);
|
||||
const jobMessagesService = Private(JobMessagesServiceProvider);
|
||||
const { isRunningOnCloud, getCloudId } = Private(cloudServiceProvider);
|
||||
$scope.isCloud = isRunningOnCloud();
|
||||
$scope.cloudId = getCloudId();
|
||||
|
|
|
@ -16,12 +16,12 @@ import { detectorToString } from 'plugins/ml/util/string_utils';
|
|||
import template from './detectors_list.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 { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlJobDetectorsList', function ($modal, $q, Private) {
|
||||
module.directive('mlJobDetectorsList', function ($modal) {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
replace: true,
|
||||
|
@ -34,7 +34,6 @@ module.directive('mlJobDetectorsList', function ($modal, $q, Private) {
|
|||
},
|
||||
template,
|
||||
controller: function ($scope) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
|
||||
$scope.addDetector = function (dtr, index) {
|
||||
if (dtr !== undefined) {
|
||||
|
|
|
@ -12,15 +12,13 @@ import 'ace';
|
|||
|
||||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
|
||||
import 'ui/courier';
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { checkLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import template from './new_job.html';
|
||||
import saveStatusTemplate from 'plugins/ml/jobs/new_job/advanced/save_status_modal/save_status_modal.html';
|
||||
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 { loadIndexPatterns, loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
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 { loadNewJobDefaults, newJobLimits, newJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
|
||||
|
@ -29,9 +27,10 @@ import {
|
|||
ML_DATA_PREVIEW_COUNT,
|
||||
basicJobValidation
|
||||
} from 'plugins/ml/../common/util/job_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
uiRoutes
|
||||
.when('/jobs/new_job/advanced', {
|
||||
|
@ -39,11 +38,12 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
indexPatterns: getIndexPatterns,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
indexPatterns: loadIndexPatterns,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
})
|
||||
.when('/jobs/new_job/advanced/:jobId', {
|
||||
|
@ -51,11 +51,12 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
indexPatterns: getIndexPatterns,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
indexPatterns: loadIndexPatterns,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -68,15 +69,10 @@ module.controller('MlNewJob',
|
|||
$route,
|
||||
$location,
|
||||
$modal,
|
||||
$q,
|
||||
courier,
|
||||
es,
|
||||
Private,
|
||||
timefilter,
|
||||
mlDatafeedService,
|
||||
mlConfirmModalService) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
|
||||
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page
|
||||
const MODE = {
|
||||
|
@ -297,7 +293,7 @@ module.controller('MlNewJob',
|
|||
};
|
||||
|
||||
function loadFields() {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clear($scope.fields);
|
||||
clear($scope.dateFields);
|
||||
clear($scope.catFields);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ml } from 'plugins/ml/services/ml_api_service';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlBucketSpanEstimator', function ($q) {
|
||||
module.directive('mlBucketSpanEstimator', function () {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
replace: false,
|
||||
|
@ -78,7 +78,7 @@ module.directive('mlBucketSpanEstimator', function ($q) {
|
|||
});
|
||||
}
|
||||
|
||||
$q.when(ml.estimateBucketSpan(data))
|
||||
ml.estimateBucketSpan(data)
|
||||
.then((interval) => {
|
||||
if (interval.error) {
|
||||
errorHandler(interval.message);
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
|
||||
|
||||
import { PostSaveServiceProvider } from './post_save_service';
|
||||
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { postSaveService } from './post_save_service';
|
||||
import { mlCreateWatchService } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { xpackFeatureProvider } from 'plugins/ml/license/check_license';
|
||||
import template from './post_save_options.html';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
@ -24,17 +25,15 @@ module.directive('mlPostSaveOptions', function (Private) {
|
|||
},
|
||||
template,
|
||||
link: function ($scope) {
|
||||
const xpackFeature = Private(xpackFeatureProvider);
|
||||
|
||||
const postSaveService = Private(PostSaveServiceProvider);
|
||||
const createWatchService = Private(CreateWatchServiceProvider);
|
||||
|
||||
$scope.watcherEnabled = createWatchService.isWatcherEnabled();
|
||||
$scope.watcherEnabled = xpackFeature.isAvailable('watcher');
|
||||
$scope.status = postSaveService.status;
|
||||
$scope.STATUS = postSaveService.STATUS;
|
||||
|
||||
createWatchService.reset();
|
||||
mlCreateWatchService.reset();
|
||||
|
||||
createWatchService.config.includeInfluencers = $scope.includeInfluencers;
|
||||
mlCreateWatchService.config.includeInfluencers = $scope.includeInfluencers;
|
||||
$scope.runInRealtime = false;
|
||||
$scope.createWatch = false;
|
||||
$scope.embedded = true;
|
||||
|
|
|
@ -7,65 +7,61 @@
|
|||
|
||||
|
||||
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlCreateWatchService } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
|
||||
export function PostSaveServiceProvider(Private, $q) {
|
||||
const msgs = mlMessageBarService;
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const createWatchService = Private(CreateWatchServiceProvider);
|
||||
const msgs = mlMessageBarService;
|
||||
|
||||
class PostSaveService {
|
||||
constructor() {
|
||||
this.STATUS = {
|
||||
SAVE_FAILED: -1,
|
||||
SAVING: 0,
|
||||
SAVED: 1,
|
||||
};
|
||||
class PostSaveService {
|
||||
constructor() {
|
||||
this.STATUS = {
|
||||
SAVE_FAILED: -1,
|
||||
SAVING: 0,
|
||||
SAVED: 1,
|
||||
};
|
||||
|
||||
this.status = {
|
||||
realtimeJob: null,
|
||||
watch: null
|
||||
};
|
||||
createWatchService.status = this.status;
|
||||
this.status = {
|
||||
realtimeJob: null,
|
||||
watch: null
|
||||
};
|
||||
mlCreateWatchService.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.externalCreateWatch;
|
||||
}
|
||||
|
||||
return new PostSaveService();
|
||||
startRealtimeJob(jobId) {
|
||||
return new Promise((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) {
|
||||
mlCreateWatchService.createNewWatch(jobId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const postSaveService = new PostSaveService();
|
||||
|
|
|
@ -1,36 +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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// calculates the size of the model memory limit used in the job config
|
||||
// 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.
|
||||
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function CalculateModelMemoryLimitProvider() {
|
||||
return function calculateModelMemoryLimit(
|
||||
indexPattern,
|
||||
splitFieldName,
|
||||
query,
|
||||
fieldNames,
|
||||
influencerNames,
|
||||
timeFieldName,
|
||||
earliestMs,
|
||||
latestMs) {
|
||||
return ml.calculateModelMemoryLimit({
|
||||
indexPattern,
|
||||
splitFieldName,
|
||||
query,
|
||||
fieldNames,
|
||||
influencerNames,
|
||||
timeFieldName,
|
||||
earliestMs,
|
||||
latestMs
|
||||
});
|
||||
};
|
||||
}
|
|
@ -11,16 +11,14 @@
|
|||
import _ from 'lodash';
|
||||
import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
|
||||
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';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import { mlSimpleJobSearchService } from 'plugins/ml/jobs/new_job/simple/components/utils/search_service';
|
||||
|
||||
export function ChartDataUtilsProvider($q, Private, timefilter) {
|
||||
export function ChartDataUtilsProvider(Private, timefilter) {
|
||||
const TimeBuckets = Private(IntervalHelperProvider);
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const mlSimpleJobSearchService = Private(SimpleJobSearchServiceProvider);
|
||||
|
||||
function loadDocCountData(formConfig, chartData) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// set doc count chart to be 10x less than detector charts
|
||||
const BAR_TARGET = Math.ceil(formConfig.chartInterval.barTarget / 10);
|
||||
const MAX_BARS = BAR_TARGET + (BAR_TARGET / 100) * 100; // 100% larger that bar target
|
||||
|
@ -79,7 +77,7 @@ export function ChartDataUtilsProvider($q, Private, timefilter) {
|
|||
}
|
||||
|
||||
function loadJobSwimlaneData(formConfig, chartData) {
|
||||
return $q((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
mlResultsService.getScoresByBucket(
|
||||
[formConfig.jobId],
|
||||
formConfig.start,
|
||||
|
@ -119,7 +117,7 @@ export function ChartDataUtilsProvider($q, Private, timefilter) {
|
|||
}
|
||||
|
||||
function loadDetectorSwimlaneData(formConfig, chartData) {
|
||||
return $q((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
mlSimpleJobSearchService.getScoresByRecord(
|
||||
formConfig.jobId,
|
||||
formConfig.start,
|
||||
|
|
|
@ -10,84 +10,83 @@ import _ from 'lodash';
|
|||
|
||||
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
|
||||
import { escapeForElasticsearchQuery } from 'plugins/ml/util/string_utils';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function SimpleJobSearchServiceProvider($q, es) {
|
||||
// detector swimlane search
|
||||
function getScoresByRecord(jobId, earliestMs, latestMs, interval, firstSplitField) {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
// detector swimlane search
|
||||
function getScoresByRecord(jobId, earliestMs, latestMs, interval, firstSplitField) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
let jobIdFilterStr = 'job_id: ' + jobId;
|
||||
if (firstSplitField && firstSplitField.value !== undefined) {
|
||||
// Escape any reserved characters for the query_string query,
|
||||
// wrapping the value in quotes to do a phrase match.
|
||||
// Backslash is a special character in JSON strings, so doubly escape
|
||||
// any backslash characters which exist in the field value.
|
||||
jobIdFilterStr += ` AND ${escapeForElasticsearchQuery(firstSplitField.name)}:`;
|
||||
jobIdFilterStr += `"${String(firstSplitField.value).replace(/\\/g, '\\\\')}"`;
|
||||
}
|
||||
let jobIdFilterStr = 'job_id: ' + jobId;
|
||||
if (firstSplitField && firstSplitField.value !== undefined) {
|
||||
// Escape any reserved characters for the query_string query,
|
||||
// wrapping the value in quotes to do a phrase match.
|
||||
// Backslash is a special character in JSON strings, so doubly escape
|
||||
// any backslash characters which exist in the field value.
|
||||
jobIdFilterStr += ` AND ${escapeForElasticsearchQuery(firstSplitField.name)}:`;
|
||||
jobIdFilterStr += `"${String(firstSplitField.value).replace(/\\/g, '\\\\')}"`;
|
||||
}
|
||||
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{
|
||||
query_string: {
|
||||
query: 'result_type:record'
|
||||
}
|
||||
}, {
|
||||
bool: {
|
||||
must: [{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{
|
||||
query_string: {
|
||||
query: 'result_type:record'
|
||||
}
|
||||
}, {
|
||||
bool: {
|
||||
must: [{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}, {
|
||||
query_string: {
|
||||
query: jobIdFilterStr
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
detector_index: {
|
||||
terms: {
|
||||
field: 'detector_index',
|
||||
order: {
|
||||
recordScore: 'desc'
|
||||
}
|
||||
}, {
|
||||
query_string: {
|
||||
query: jobIdFilterStr
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
detector_index: {
|
||||
terms: {
|
||||
field: 'detector_index',
|
||||
order: {
|
||||
recordScore: 'desc'
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
recordScore: {
|
||||
max: {
|
||||
field: 'record_score'
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
recordScore: {
|
||||
max: {
|
||||
field: 'record_score'
|
||||
byTime: {
|
||||
date_histogram: {
|
||||
field: 'timestamp',
|
||||
interval: interval,
|
||||
min_doc_count: 1,
|
||||
extended_bounds: {
|
||||
min: earliestMs,
|
||||
max: latestMs
|
||||
}
|
||||
},
|
||||
byTime: {
|
||||
date_histogram: {
|
||||
field: 'timestamp',
|
||||
interval: interval,
|
||||
min_doc_count: 1,
|
||||
extended_bounds: {
|
||||
min: earliestMs,
|
||||
max: latestMs
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
recordScore: {
|
||||
max: {
|
||||
field: 'record_score'
|
||||
}
|
||||
aggs: {
|
||||
recordScore: {
|
||||
max: {
|
||||
field: 'record_score'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,73 +94,74 @@ export function SimpleJobSearchServiceProvider($q, es) {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
const detectorsByIndex = _.get(resp, ['aggregations', 'detector_index', 'buckets'], []);
|
||||
_.each(detectorsByIndex, (dtr) => {
|
||||
const dtrResults = {};
|
||||
const dtrIndex = +dtr.key;
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
const detectorsByIndex = _.get(resp, ['aggregations', 'detector_index', 'buckets'], []);
|
||||
_.each(detectorsByIndex, (dtr) => {
|
||||
const dtrResults = {};
|
||||
const dtrIndex = +dtr.key;
|
||||
|
||||
const buckets = _.get(dtr, ['byTime', 'buckets'], []);
|
||||
for (let j = 0; j < buckets.length; j++) {
|
||||
const bkt = buckets[j];
|
||||
const time = bkt.key;
|
||||
dtrResults[time] = {
|
||||
recordScore: _.get(bkt, ['recordScore', 'value']),
|
||||
};
|
||||
}
|
||||
obj.results[dtrIndex] = dtrResults;
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
const buckets = _.get(dtr, ['byTime', 'buckets'], []);
|
||||
for (let j = 0; j < buckets.length; j++) {
|
||||
const bkt = buckets[j];
|
||||
const time = bkt.key;
|
||||
dtrResults[time] = {
|
||||
recordScore: _.get(bkt, ['recordScore', 'value']),
|
||||
};
|
||||
}
|
||||
obj.results[dtrIndex] = dtrResults;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getCategoryFields(index, field, size, query) {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
es.search({
|
||||
index,
|
||||
size: 0,
|
||||
body: {
|
||||
query: query,
|
||||
aggs: {
|
||||
catFields: {
|
||||
terms: {
|
||||
field: field,
|
||||
size: size
|
||||
}
|
||||
function getCategoryFields(index, field, size, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
ml.esSearch({
|
||||
index,
|
||||
size: 0,
|
||||
body: {
|
||||
query: query,
|
||||
aggs: {
|
||||
catFields: {
|
||||
terms: {
|
||||
field: field,
|
||||
size: size
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
obj.results.values = [];
|
||||
const catFields = _.get(resp, ['aggregations', 'catFields', 'buckets'], []);
|
||||
_.each(catFields, (f) => {
|
||||
obj.results.values.push(f.key);
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
obj.results.values = [];
|
||||
const catFields = _.get(resp, ['aggregations', 'catFields', 'buckets'], []);
|
||||
_.each(catFields, (f) => {
|
||||
obj.results.values.push(f.key);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getScoresByRecord,
|
||||
getCategoryFields
|
||||
};
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export const mlSimpleJobSearchService = {
|
||||
getScoresByRecord,
|
||||
getCategoryFields
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
import { CreateWatchServiceProvider } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
import { mlCreateWatchService } from 'plugins/ml/jobs/new_job/simple/components/watcher/create_watch_service';
|
||||
|
||||
import template from './create_watch.html';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
@ -16,7 +16,7 @@ import { ml } from 'plugins/ml/services/ml_api_service';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.directive('mlCreateWatch', function (es, $q, Private) {
|
||||
module.directive('mlCreateWatch', function () {
|
||||
return {
|
||||
restrict: 'AE',
|
||||
replace: false,
|
||||
|
@ -27,7 +27,6 @@ module.directive('mlCreateWatch', function (es, $q, Private) {
|
|||
},
|
||||
template,
|
||||
link: function ($scope) {
|
||||
const mlCreateWatchService = Private(CreateWatchServiceProvider);
|
||||
$scope.config = mlCreateWatchService.config;
|
||||
$scope.status = mlCreateWatchService.status;
|
||||
$scope.STATUS = mlCreateWatchService.STATUS;
|
||||
|
@ -58,7 +57,7 @@ module.directive('mlCreateWatch', function (es, $q, Private) {
|
|||
}
|
||||
|
||||
// load elasticsearch settings to see if email has been configured
|
||||
$q.when(ml.getNotificationSettings()).then((resp) => {
|
||||
ml.getNotificationSettings().then((resp) => {
|
||||
if (_.has(resp, 'defaults.xpack.notification.email')) {
|
||||
$scope.ui.emailEnabled = true;
|
||||
}
|
||||
|
|
|
@ -8,152 +8,146 @@
|
|||
|
||||
import chrome from 'ui/chrome';
|
||||
import _ from 'lodash';
|
||||
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
|
||||
import { http } from 'plugins/ml/services/http_service';
|
||||
|
||||
import emailBody from './email.html';
|
||||
import emailInfluencersBody from './email-influencers.html';
|
||||
import { watch } from './watch.js';
|
||||
|
||||
export function CreateWatchServiceProvider($http, $q, Private) {
|
||||
|
||||
const xpackInfo = Private(XPackInfoProvider);
|
||||
const compiledEmailBody = _.template(emailBody);
|
||||
|
||||
const compiledEmailBody = _.template(emailBody);
|
||||
|
||||
const emailSection = {
|
||||
send_email: {
|
||||
throttle_period_in_millis: 900000, // 15m
|
||||
email: {
|
||||
profile: 'standard',
|
||||
to: [],
|
||||
subject: 'ML Watcher Alert',
|
||||
body: {
|
||||
html: ''
|
||||
}
|
||||
const emailSection = {
|
||||
send_email: {
|
||||
throttle_period_in_millis: 900000, // 15m
|
||||
email: {
|
||||
profile: 'standard',
|
||||
to: [],
|
||||
subject: 'ML Watcher Alert',
|
||||
body: {
|
||||
html: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// generate a random number between min and max
|
||||
function randomNumber(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
if (jobId !== undefined) {
|
||||
const id = `ml-${jobId}`;
|
||||
this.config.id = id;
|
||||
|
||||
// set specific properties of the the watch
|
||||
watch.input.search.request.body.query.bool.filter[0].term.job_id = jobId;
|
||||
watch.input.search.request.body.query.bool.filter[1].range.timestamp.gte = `now-${this.config.interval}`;
|
||||
watch.input.search.request.body.aggs.bucket_results.filter.range.anomaly_score.gte = this.config.threshold.val;
|
||||
|
||||
if (this.config.includeEmail && this.config.email !== '') {
|
||||
const emails = this.config.email.split(',');
|
||||
emailSection.send_email.email.to = emails;
|
||||
|
||||
// create the html by adding the variables to the compiled email body.
|
||||
emailSection.send_email.email.body.html = compiledEmailBody({
|
||||
serverAddress: chrome.getAppUrl(),
|
||||
influencersSection: ((this.config.includeInfluencers === true) ? emailInfluencersBody : '')
|
||||
});
|
||||
|
||||
// add email section to watch
|
||||
watch.actions.send_email = emailSection.send_email;
|
||||
}
|
||||
|
||||
// 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
|
||||
// and the watches synchronize
|
||||
const triggerInterval = randomNumber(60, 120);
|
||||
watch.trigger.schedule.interval = `${triggerInterval}s`;
|
||||
|
||||
const watchModel = {
|
||||
id,
|
||||
upstreamJSON: {
|
||||
id,
|
||||
type: 'json',
|
||||
watch
|
||||
}
|
||||
};
|
||||
|
||||
if (id !== '') {
|
||||
saveWatch(watchModel)
|
||||
.then(() => {
|
||||
this.status.watch = this.STATUS.SAVED;
|
||||
this.config.watcherEditURL =
|
||||
`${chrome.getBasePath()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`;
|
||||
resolve();
|
||||
})
|
||||
.catch((resp) => {
|
||||
this.status.watch = this.STATUS.SAVE_FAILED;
|
||||
reject(resp);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.status.watch = this.STATUS.SAVE_FAILED;
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isWatcherEnabled() {
|
||||
return xpackInfo.get('features.watcher.isAvailable', false);
|
||||
}
|
||||
|
||||
loadWatch(jobId) {
|
||||
const id = `ml-${jobId}`;
|
||||
const basePath = chrome.addBasePath('/api/watcher');
|
||||
const url = `${basePath}/watch/${id}`;
|
||||
return $http.get(url)
|
||||
.catch(e => {
|
||||
throw e.data.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new CreateWatchService();
|
||||
// generate a random number between min and max
|
||||
function randomNumber(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
function saveWatch(watchModel) {
|
||||
const basePath = chrome.addBasePath('/api/watcher');
|
||||
const url = `${basePath}/watch/${watchModel.id}`;
|
||||
|
||||
return http({
|
||||
url,
|
||||
method: 'PUT',
|
||||
data: watchModel.upstreamJSON
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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 new Promise((resolve, reject) => {
|
||||
this.status.watch = this.STATUS.SAVING;
|
||||
if (jobId !== undefined) {
|
||||
const id = `ml-${jobId}`;
|
||||
this.config.id = id;
|
||||
|
||||
// set specific properties of the the watch
|
||||
watch.input.search.request.body.query.bool.filter[0].term.job_id = jobId;
|
||||
watch.input.search.request.body.query.bool.filter[1].range.timestamp.gte = `now-${this.config.interval}`;
|
||||
watch.input.search.request.body.aggs.bucket_results.filter.range.anomaly_score.gte = this.config.threshold.val;
|
||||
|
||||
if (this.config.includeEmail && this.config.email !== '') {
|
||||
const emails = this.config.email.split(',');
|
||||
emailSection.send_email.email.to = emails;
|
||||
|
||||
// create the html by adding the variables to the compiled email body.
|
||||
emailSection.send_email.email.body.html = compiledEmailBody({
|
||||
serverAddress: chrome.getAppUrl(),
|
||||
influencersSection: ((this.config.includeInfluencers === true) ? emailInfluencersBody : '')
|
||||
});
|
||||
|
||||
// add email section to watch
|
||||
watch.actions.send_email = emailSection.send_email;
|
||||
}
|
||||
|
||||
// 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
|
||||
// and the watches synchronize
|
||||
const triggerInterval = randomNumber(60, 120);
|
||||
watch.trigger.schedule.interval = `${triggerInterval}s`;
|
||||
|
||||
const watchModel = {
|
||||
id,
|
||||
upstreamJSON: {
|
||||
id,
|
||||
type: 'json',
|
||||
watch
|
||||
}
|
||||
};
|
||||
|
||||
if (id !== '') {
|
||||
saveWatch(watchModel)
|
||||
.then(() => {
|
||||
this.status.watch = this.STATUS.SAVED;
|
||||
this.config.watcherEditURL =
|
||||
`${chrome.getBasePath()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`;
|
||||
resolve();
|
||||
})
|
||||
.catch((resp) => {
|
||||
this.status.watch = this.STATUS.SAVE_FAILED;
|
||||
reject(resp);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.status.watch = this.STATUS.SAVE_FAILED;
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadWatch(jobId) {
|
||||
const id = `ml-${jobId}`;
|
||||
const basePath = chrome.addBasePath('/api/watcher');
|
||||
const url = `${basePath}/watch/${id}`;
|
||||
return http({
|
||||
url,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const mlCreateWatchService = new CreateWatchService();
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import angular from 'angular';
|
|||
import uiRoutes from 'ui/routes';
|
||||
import { checkLicenseExpired } from 'plugins/ml/license/check_license';
|
||||
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { CalculateModelMemoryLimitProvider } from 'plugins/ml/jobs/new_job/simple/components/utils/calculate_memory_limit';
|
||||
import { IntervalHelperProvider } from 'plugins/ml/util/ml_time_buckets';
|
||||
import { filterAggTypes } from 'plugins/ml/jobs/new_job/simple/components/utils/filter_agg_types';
|
||||
import { validateJob } from 'plugins/ml/jobs/new_job/simple/components/utils/validate_job';
|
||||
|
@ -27,7 +26,7 @@ import { adjustIntervalDisplayed } from 'plugins/ml/jobs/new_job/simple/componen
|
|||
import { populateAppStateSettings } from 'plugins/ml/jobs/new_job/simple/components/utils/app_state_settings';
|
||||
import { CHART_STATE, JOB_STATE } from 'plugins/ml/jobs/new_job/simple/components/constants/states';
|
||||
import { createFields } from 'plugins/ml/jobs/new_job/simple/components/utils/create_fields';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { ChartDataUtilsProvider } from 'plugins/ml/jobs/new_job/simple/components/utils/chart_data_utils.js';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
|
||||
|
@ -37,10 +36,12 @@ import {
|
|||
createResultsUrl,
|
||||
addNewJobToRecentlyAccessed,
|
||||
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } 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 { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import template from './create_job.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -49,10 +50,11 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -72,9 +74,7 @@ module
|
|||
const msgs = mlMessageBarService;
|
||||
const MlTimeBuckets = Private(IntervalHelperProvider);
|
||||
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
|
||||
const calculateModelMemoryLimit = Private(CalculateModelMemoryLimitProvider);
|
||||
const chartDataUtils = Private(ChartDataUtilsProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlMultiMetricJobService = Private(MultiMetricJobServiceProvider);
|
||||
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
|
||||
$scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed;
|
||||
|
@ -641,18 +641,18 @@ module
|
|||
|
||||
$scope.setModelMemoryLimit = function () {
|
||||
const formConfig = $scope.formConfig;
|
||||
calculateModelMemoryLimit(
|
||||
formConfig.indexPattern.title,
|
||||
formConfig.splitField.name,
|
||||
formConfig.query,
|
||||
Object.keys(formConfig.fields),
|
||||
formConfig.influencerFields.map(f => f.name),
|
||||
formConfig.timeField,
|
||||
formConfig.start,
|
||||
formConfig.end
|
||||
)
|
||||
ml.calculateModelMemoryLimit({
|
||||
indexPattern: formConfig.indexPattern.title,
|
||||
splitFieldName: formConfig.splitField.name,
|
||||
query: formConfig.query,
|
||||
fieldNames: Object.keys(formConfig.fields),
|
||||
influencerNames: formConfig.influencerFields.map(f => f.name),
|
||||
timeFieldName: formConfig.timeField,
|
||||
earliestMs: formConfig.start,
|
||||
latestMs: formConfig.end
|
||||
})
|
||||
.then((resp) => {
|
||||
formConfig.modelMemoryLimit = resp;
|
||||
formConfig.modelMemoryLimit = resp.modelMemoryLimit;
|
||||
})
|
||||
.catch(() => {
|
||||
formConfig.modelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT;
|
||||
|
|
|
@ -10,18 +10,12 @@ import _ from 'lodash';
|
|||
|
||||
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 { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function MultiMetricJobServiceProvider(
|
||||
$q,
|
||||
es,
|
||||
timefilter,
|
||||
Private) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const fieldFormatService = Private(FieldFormatServiceProvider);
|
||||
export function MultiMetricJobServiceProvider() {
|
||||
|
||||
class MultiMetricJobService {
|
||||
|
||||
|
@ -57,7 +51,7 @@ export function MultiMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
getLineChartResults(formConfig, thisLoadTimestamp) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const fieldIds = Object.keys(formConfig.fields).sort();
|
||||
|
||||
|
@ -80,7 +74,7 @@ export function MultiMetricJobServiceProvider(
|
|||
|
||||
const searchJson = getSearchJsonFromConfig(formConfig);
|
||||
|
||||
es.search(searchJson)
|
||||
ml.esSearch(searchJson)
|
||||
.then((resp) => {
|
||||
// if this is the last chart load, wipe all previous chart data
|
||||
if (thisLoadTimestamp === this.chartData.lastLoadTimestamp) {
|
||||
|
@ -94,7 +88,7 @@ export function MultiMetricJobServiceProvider(
|
|||
if (fieldId !== EVENT_RATE_COUNT_FIELD) {
|
||||
const field = formConfig.fields[fieldId];
|
||||
const aggType = field.agg.type.dslName;
|
||||
this.chartData.detectors[fieldId].fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
|
||||
this.chartData.detectors[fieldId].fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern(
|
||||
formConfig.indexPattern,
|
||||
fieldId,
|
||||
aggType);
|
||||
|
@ -245,7 +239,7 @@ export function MultiMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
createJob(formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.job = this.getJobFromConfig(formConfig);
|
||||
const job = createJobForSaving(this.job);
|
||||
|
|
|
@ -26,7 +26,7 @@ import { adjustIntervalDisplayed } from 'plugins/ml/jobs/new_job/simple/componen
|
|||
import { populateAppStateSettings } from 'plugins/ml/jobs/new_job/simple/components/utils/app_state_settings';
|
||||
import { CHART_STATE, JOB_STATE } from 'plugins/ml/jobs/new_job/simple/components/constants/states';
|
||||
import { createFields } from 'plugins/ml/jobs/new_job/simple/components/utils/create_fields';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { ChartDataUtilsProvider } from 'plugins/ml/jobs/new_job/simple/components/utils/chart_data_utils.js';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { loadNewJobDefaults, newJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
|
||||
|
@ -36,10 +36,11 @@ import {
|
|||
createResultsUrl,
|
||||
addNewJobToRecentlyAccessed,
|
||||
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } 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 { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './create_job.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -48,10 +49,11 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -63,7 +65,6 @@ module
|
|||
$scope,
|
||||
$route,
|
||||
$timeout,
|
||||
$q,
|
||||
timefilter,
|
||||
Private,
|
||||
AppState) {
|
||||
|
@ -74,7 +75,6 @@ module
|
|||
const MlTimeBuckets = Private(IntervalHelperProvider);
|
||||
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
|
||||
const chartDataUtils = Private(ChartDataUtilsProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlPopulationJobService = Private(PopulationJobServiceProvider);
|
||||
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
|
||||
$scope.addNewJobToRecentlyAccessed = addNewJobToRecentlyAccessed;
|
||||
|
@ -227,7 +227,7 @@ module
|
|||
};
|
||||
|
||||
$scope.splitChange = function (fieldIndex, splitField) {
|
||||
return $q((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
$scope.formConfig.fields[fieldIndex].firstSplitFieldName = undefined;
|
||||
|
||||
if (splitField !== undefined) {
|
||||
|
|
|
@ -11,20 +11,14 @@ import _ from 'lodash';
|
|||
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 { 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 { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function PopulationJobServiceProvider(timefilter, Private) {
|
||||
|
||||
export function PopulationJobServiceProvider(
|
||||
$q,
|
||||
es,
|
||||
timefilter,
|
||||
Private) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const TimeBuckets = Private(IntervalHelperProvider);
|
||||
const fieldFormatService = Private(FieldFormatServiceProvider);
|
||||
const OVER_FIELD_EXAMPLES_COUNT = 40;
|
||||
|
||||
class PopulationJobService {
|
||||
|
@ -61,7 +55,7 @@ export function PopulationJobServiceProvider(
|
|||
}
|
||||
|
||||
getLineChartResults(formConfig, thisLoadTimestamp) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const fieldIds = formConfig.fields.map(f => f.id);
|
||||
|
||||
|
@ -84,7 +78,7 @@ export function PopulationJobServiceProvider(
|
|||
|
||||
const searchJson = getSearchJsonFromConfig(formConfig, timefilter, TimeBuckets);
|
||||
|
||||
es.search(searchJson)
|
||||
ml.esSearch(searchJson)
|
||||
.then((resp) => {
|
||||
// if this is the last chart load, wipe all previous chart data
|
||||
if (thisLoadTimestamp === this.chartData.lastLoadTimestamp) {
|
||||
|
@ -99,7 +93,7 @@ export function PopulationJobServiceProvider(
|
|||
if (fieldId !== EVENT_RATE_COUNT_FIELD) {
|
||||
const field = formConfig.fields[i];
|
||||
const aggType = field.agg.type.dslName;
|
||||
this.chartData.detectors[i].fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
|
||||
this.chartData.detectors[i].fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern(
|
||||
formConfig.indexPattern,
|
||||
fieldId,
|
||||
aggType);
|
||||
|
@ -277,7 +271,7 @@ export function PopulationJobServiceProvider(
|
|||
}
|
||||
|
||||
createJob(formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.job = this.getJobFromConfig(formConfig);
|
||||
const job = createJobForSaving(this.job);
|
||||
|
|
|
@ -17,12 +17,13 @@ import 'plugins/kibana/visualize/styles/main.less';
|
|||
import uiRoutes from 'ui/routes';
|
||||
import { checkLicenseExpired } from 'plugins/ml/license/check_license';
|
||||
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch } from 'plugins/ml/util/index_utils';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { CreateRecognizerJobsServiceProvider } from './create_job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './create_job.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -31,9 +32,10 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
checkMlNodesAvailable
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -45,11 +47,9 @@ module
|
|||
$scope,
|
||||
$window,
|
||||
$route,
|
||||
$q,
|
||||
timefilter,
|
||||
Private) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlCreateRecognizerJobsService = Private(CreateRecognizerJobsServiceProvider);
|
||||
timefilter.disableTimeRangeSelector();
|
||||
timefilter.disableAutoRefreshSelector();
|
||||
|
@ -147,7 +147,7 @@ module
|
|||
function loadJobConfigs() {
|
||||
// load the job and datafeed configs as well as the kibana saved objects
|
||||
// from the recognizer endpoint
|
||||
$q.when(ml.getDataRecognizerModule({ moduleId }))
|
||||
ml.getDataRecognizerModule({ moduleId })
|
||||
.then(resp => {
|
||||
// populate the jobs and datafeeds
|
||||
if (resp.jobs && resp.jobs.length) {
|
||||
|
@ -250,7 +250,7 @@ module
|
|||
|
||||
// call the the setupModuleConfigs endpoint to create the jobs, datafeeds and saved objects
|
||||
function saveDataRecognizerItems() {
|
||||
return $q((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
// set all jobs, datafeeds and saved objects to a SAVING state
|
||||
// i.e. display spinners
|
||||
setAllToSaving();
|
||||
|
@ -261,7 +261,7 @@ module
|
|||
const tempQuery = (savedSearch.id === undefined) ?
|
||||
undefined : combinedQuery;
|
||||
|
||||
$q.when(ml.setupDataRecognizerConfig({ moduleId, prefix, groups, query: tempQuery, indexPatternName }))
|
||||
ml.setupDataRecognizerConfig({ moduleId, prefix, groups, query: tempQuery, indexPatternName })
|
||||
.then((resp) => {
|
||||
if (resp.jobs) {
|
||||
$scope.formConfig.jobs.forEach((job) => {
|
||||
|
@ -339,7 +339,7 @@ module
|
|||
}
|
||||
|
||||
function startDatafeeds() {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const jobs = $scope.formConfig.jobs;
|
||||
const numberOfJobs = jobs.length;
|
||||
|
@ -436,7 +436,7 @@ module
|
|||
}
|
||||
|
||||
function checkForSavedObject(type, savedObject) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let exists = false;
|
||||
mlCreateRecognizerJobsService.loadExistingSavedObjects(type)
|
||||
.then((resp) => {
|
||||
|
|
|
@ -8,19 +8,18 @@
|
|||
|
||||
import { getQueryFromSavedSearch } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function CreateRecognizerJobsServiceProvider(Private, $q) {
|
||||
export function CreateRecognizerJobsServiceProvider(Private) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
class CreateRecognizerJobsService {
|
||||
|
||||
constructor() {}
|
||||
|
||||
createDatafeed(job, formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const jobId = formConfig.jobLabel + job.id;
|
||||
|
||||
mlJobService.saveNewDatafeed(job.datafeedConfig, jobId)
|
||||
|
|
|
@ -28,7 +28,7 @@ import { populateAppStateSettings } from 'plugins/ml/jobs/new_job/simple/compone
|
|||
import { getIndexedFields } from 'plugins/ml/jobs/new_job/simple/components/utils/create_fields';
|
||||
import { changeJobIDCase } from 'plugins/ml/jobs/new_job/simple/components/general_job_details/change_job_id_case';
|
||||
import { CHART_STATE, JOB_STATE } from 'plugins/ml/jobs/new_job/simple/components/constants/states';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults';
|
||||
import {
|
||||
|
@ -36,10 +36,11 @@ import {
|
|||
createResultsUrl,
|
||||
addNewJobToRecentlyAccessed,
|
||||
moveToAdvancedJobCreationProvider } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } 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 { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
import template from './create_job.html';
|
||||
|
||||
|
@ -49,10 +50,11 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
loadNewJobDefaults
|
||||
loadNewJobDefaults,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -64,7 +66,6 @@ module
|
|||
$scope,
|
||||
$route,
|
||||
$filter,
|
||||
$q,
|
||||
timefilter,
|
||||
Private,
|
||||
AppState) {
|
||||
|
@ -74,7 +75,6 @@ module
|
|||
const msgs = mlMessageBarService;
|
||||
const MlTimeBuckets = Private(IntervalHelperProvider);
|
||||
const moveToAdvancedJobCreation = Private(moveToAdvancedJobCreationProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlSingleMetricJobService = Private(SingleMetricJobServiceProvider);
|
||||
const mlFullTimeRangeSelectorService = Private(FullTimeRangeSelectorServiceProvider);
|
||||
|
||||
|
|
|
@ -12,19 +12,13 @@ import { parseInterval } from 'ui/utils/parse_interval';
|
|||
|
||||
import { ML_MEDIAN_PERCENTS } from 'plugins/ml/../common/util/job_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 { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import { createJobForSaving } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function SingleMetricJobServiceProvider(
|
||||
$q,
|
||||
es,
|
||||
Private) {
|
||||
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const fieldFormatService = Private(FieldFormatServiceProvider);
|
||||
export function SingleMetricJobServiceProvider() {
|
||||
|
||||
class SingleMetricJobService {
|
||||
|
||||
|
@ -43,7 +37,7 @@ export function SingleMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
getLineChartResults(formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.chartData.line = [];
|
||||
this.chartData.model = [];
|
||||
|
@ -56,7 +50,7 @@ export function SingleMetricJobServiceProvider(
|
|||
|
||||
const aggType = formConfig.agg.type.dslName;
|
||||
if (formConfig.field && formConfig.field.id) {
|
||||
this.chartData.fieldFormat = fieldFormatService.getFieldFormatFromIndexPattern(
|
||||
this.chartData.fieldFormat = mlFieldFormatService.getFieldFormatFromIndexPattern(
|
||||
formConfig.indexPattern,
|
||||
formConfig.field.id,
|
||||
aggType);
|
||||
|
@ -71,7 +65,7 @@ export function SingleMetricJobServiceProvider(
|
|||
|
||||
const searchJson = getSearchJsonFromConfig(formConfig);
|
||||
|
||||
es.search(searchJson)
|
||||
ml.esSearch(searchJson)
|
||||
.then((resp) => {
|
||||
|
||||
const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []);
|
||||
|
@ -271,7 +265,7 @@ export function SingleMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
createJob(formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.job = this.getJobFromConfig(formConfig);
|
||||
const job = createJobForSaving(this.job);
|
||||
|
@ -304,7 +298,7 @@ export function SingleMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
loadModelData(formConfig) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let start = formConfig.start;
|
||||
|
||||
|
@ -362,7 +356,7 @@ export function SingleMetricJobServiceProvider(
|
|||
}
|
||||
|
||||
loadSwimlaneData(formConfig) {
|
||||
return $q((resolve) => {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
mlResultsService.getScoresByBucket(
|
||||
[formConfig.jobId],
|
||||
|
|
|
@ -10,7 +10,7 @@ import _ from 'lodash';
|
|||
import moment from 'moment';
|
||||
import { migrateFilter } from 'ui/courier/data_source/_migrate_filter.js';
|
||||
import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
export function getQueryFromSavedSearch(formConfig) {
|
||||
const must = [];
|
||||
|
@ -115,8 +115,7 @@ export function addNewJobToRecentlyAccessed(jobId, resultsUrl) {
|
|||
addItemToRecentlyAccessed(urlParts[1], jobId, urlParts[2]);
|
||||
}
|
||||
|
||||
export function moveToAdvancedJobCreationProvider(Private, $location) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
export function moveToAdvancedJobCreationProvider($location) {
|
||||
return function moveToAdvancedJobCreation(job) {
|
||||
mlJobService.currentJob = job;
|
||||
$location.path('jobs/new_job/advanced');
|
||||
|
|
|
@ -15,8 +15,9 @@ import uiRoutes from 'ui/routes';
|
|||
import { checkLicenseExpired } from 'plugins/ml/license/check_license';
|
||||
import { preConfiguredJobRedirect } from 'plugins/ml/jobs/new_job/wizard/preconfigured_job_redirect';
|
||||
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import { loadIndexPatterns, getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './index_or_search.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -30,9 +31,10 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPatterns: getIndexPatterns,
|
||||
indexPatterns: loadIndexPatterns,
|
||||
preConfiguredJobRedirect,
|
||||
checkMlNodesAvailable
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -48,7 +50,7 @@ module.controller('MlNewJobStepIndexOrSearch',
|
|||
timefilter.disableTimeRangeSelector(); // remove time picker from top of page
|
||||
timefilter.disableAutoRefreshSelector(); // remove time picker from top of page
|
||||
|
||||
$scope.indexPatterns = $route.current.locals.indexPatterns;
|
||||
$scope.indexPatterns = getIndexPatterns();
|
||||
|
||||
$scope.withIndexPatternUrl = function (pattern) {
|
||||
if (!pattern) {
|
||||
|
|
|
@ -16,9 +16,10 @@ import uiRoutes from 'ui/routes';
|
|||
import { checkLicenseExpired } from 'plugins/ml/license/check_license';
|
||||
import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { createSearchItems } from 'plugins/ml/jobs/new_job/utils/new_job_utils';
|
||||
import { getIndexPatternWithRoute, getSavedSearchWithRoute, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { loadCurrentIndexPattern, loadCurrentSavedSearch, timeBasedIndexCheck } from 'plugins/ml/util/index_utils';
|
||||
import { addItemToRecentlyAccessed } from 'plugins/ml/util/recently_accessed';
|
||||
import { checkMlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import template from './job_type.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -27,9 +28,10 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicenseExpired,
|
||||
privileges: checkCreateJobsPrivilege,
|
||||
indexPattern: getIndexPatternWithRoute,
|
||||
savedSearch: getSavedSearchWithRoute,
|
||||
checkMlNodesAvailable
|
||||
indexPattern: loadCurrentIndexPattern,
|
||||
savedSearch: loadCurrentSavedSearch,
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import chrome from 'ui/chrome';
|
|||
|
||||
let licenseHasExpired = true;
|
||||
|
||||
export function checkLicense(Private, Promise, kbnBaseUrl) {
|
||||
export function checkLicense(Private, kbnBaseUrl) {
|
||||
const xpackInfo = Private(XPackInfoProvider);
|
||||
const features = xpackInfo.get('features.ml');
|
||||
|
||||
|
@ -67,3 +67,14 @@ export function checkLicenseExpired(Private, Promise, kbnBaseUrl, kbnUrl) {
|
|||
export function getLicenseHasExpired() {
|
||||
return licenseHasExpired;
|
||||
}
|
||||
|
||||
export function xpackFeatureProvider(Private) {
|
||||
const xpackInfo = Private(XPackInfoProvider);
|
||||
function isAvailable(feature) {
|
||||
return xpackInfo.get(`features.${feature}.isAvailable`, false);
|
||||
}
|
||||
|
||||
return {
|
||||
isAvailable
|
||||
};
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export function getMlNodeCount() {
|
|||
})
|
||||
.catch((error) => {
|
||||
mlNodeCount = 0;
|
||||
if (error.statusCode === 403) {
|
||||
if (error.status === 403) {
|
||||
userHasPermissionToViewMlNodeCount = false;
|
||||
} else {
|
||||
console.error(error);
|
||||
|
|
|
@ -9,92 +9,86 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
|
||||
let calendarService = undefined;
|
||||
|
||||
export function CalendarServiceProvider($q, Private) {
|
||||
const msgs = mlMessageBarService;
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
|
||||
class CalendarService {
|
||||
constructor() {
|
||||
this.calendars = [];
|
||||
// list of calendar ids per job id
|
||||
this.jobCalendars = {};
|
||||
// list of calendar ids per group id
|
||||
this.groupCalendars = {};
|
||||
}
|
||||
const msgs = mlMessageBarService;
|
||||
|
||||
loadCalendars(jobs) {
|
||||
return $q((resolve, reject) => {
|
||||
let calendars = [];
|
||||
jobs.forEach((j) => {
|
||||
this.jobCalendars[j.job_id] = [];
|
||||
});
|
||||
const groups = {};
|
||||
mlJobService.getJobGroups().forEach((g) => {
|
||||
groups[g.id] = g;
|
||||
});
|
||||
class CalendarService {
|
||||
constructor() {
|
||||
this.calendars = [];
|
||||
// list of calendar ids per job id
|
||||
this.jobCalendars = {};
|
||||
// list of calendar ids per group id
|
||||
this.groupCalendars = {};
|
||||
}
|
||||
|
||||
ml.calendars()
|
||||
.then((resp) => {
|
||||
calendars = resp;
|
||||
// loop through calendars and their job_ids and create jobCalendars
|
||||
// if a group is found, expand it out to its member jobs
|
||||
calendars.forEach((cal) => {
|
||||
cal.job_ids.forEach((id) => {
|
||||
let isGroup = false;
|
||||
// the job_id could be either a job id or a group id
|
||||
if (this.jobCalendars[id] !== undefined) {
|
||||
this.jobCalendars[id].push(cal.calendar_id);
|
||||
} else if (groups[id] !== undefined) {
|
||||
isGroup = true;
|
||||
// expand out the group into its jobs and add each job
|
||||
groups[id].jobs.forEach((j) => {
|
||||
this.jobCalendars[j.job_id].push(cal.calendar_id);
|
||||
});
|
||||
} else {
|
||||
// not a known job or a known group. assume it's a unused group
|
||||
isGroup = true;
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
// keep track of calendars per group
|
||||
if (this.groupCalendars[id] === undefined) {
|
||||
this.groupCalendars[id] = [cal.calendar_id];
|
||||
} else {
|
||||
this.groupCalendars[id].push(cal.calendar_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// deduplicate as group expansion may have added dupes.
|
||||
_.each(this.jobCalendars, (cal, id) => {
|
||||
this.jobCalendars[id] = _.uniq(cal);
|
||||
});
|
||||
|
||||
this.calendars = calendars;
|
||||
resolve({ calendars });
|
||||
})
|
||||
.catch((err) => {
|
||||
msgs.error('Calendars list could not be retrieved');
|
||||
msgs.error('', err);
|
||||
reject({ calendars, err });
|
||||
});
|
||||
loadCalendars(jobs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let calendars = [];
|
||||
jobs.forEach((j) => {
|
||||
this.jobCalendars[j.job_id] = [];
|
||||
});
|
||||
const groups = {};
|
||||
mlJobService.getJobGroups().forEach((g) => {
|
||||
groups[g.id] = g;
|
||||
});
|
||||
}
|
||||
|
||||
// get the list of calendar groups
|
||||
getCalendarGroups() {
|
||||
return Object.keys(this.groupCalendars).map(id => ({ id }));
|
||||
}
|
||||
ml.calendars()
|
||||
.then((resp) => {
|
||||
calendars = resp;
|
||||
// loop through calendars and their job_ids and create jobCalendars
|
||||
// if a group is found, expand it out to its member jobs
|
||||
calendars.forEach((cal) => {
|
||||
cal.job_ids.forEach((id) => {
|
||||
let isGroup = false;
|
||||
// the job_id could be either a job id or a group id
|
||||
if (this.jobCalendars[id] !== undefined) {
|
||||
this.jobCalendars[id].push(cal.calendar_id);
|
||||
} else if (groups[id] !== undefined) {
|
||||
isGroup = true;
|
||||
// expand out the group into its jobs and add each job
|
||||
groups[id].jobs.forEach((j) => {
|
||||
this.jobCalendars[j.job_id].push(cal.calendar_id);
|
||||
});
|
||||
} else {
|
||||
// not a known job or a known group. assume it's a unused group
|
||||
isGroup = true;
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
// keep track of calendars per group
|
||||
if (this.groupCalendars[id] === undefined) {
|
||||
this.groupCalendars[id] = [cal.calendar_id];
|
||||
} else {
|
||||
this.groupCalendars[id].push(cal.calendar_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// deduplicate as group expansion may have added dupes.
|
||||
_.each(this.jobCalendars, (cal, id) => {
|
||||
this.jobCalendars[id] = _.uniq(cal);
|
||||
});
|
||||
|
||||
this.calendars = calendars;
|
||||
resolve({ calendars });
|
||||
})
|
||||
.catch((err) => {
|
||||
msgs.error('Calendars list could not be retrieved');
|
||||
msgs.error('', err);
|
||||
reject({ calendars, err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (calendarService === undefined) {
|
||||
calendarService = new CalendarService();
|
||||
// get the list of calendar groups
|
||||
getCalendarGroups() {
|
||||
return Object.keys(this.groupCalendars).map(id => ({ id }));
|
||||
}
|
||||
return calendarService;
|
||||
}
|
||||
|
||||
export const mlCalendarService = new CalendarService();
|
||||
|
|
|
@ -8,125 +8,117 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
import 'ui/courier';
|
||||
import { mlFunctionToESAggregation } from 'plugins/ml/../common/util/job_utils';
|
||||
import { getIndexPatternProvider } from 'plugins/ml/util/index_utils';
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { getIndexPatternById } from 'plugins/ml/util/index_utils';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
|
||||
// Service for accessing FieldFormat objects configured for a Kibana index pattern
|
||||
// for use in formatting the actual and typical values from anomalies.
|
||||
export function FieldFormatServiceProvider(
|
||||
$q,
|
||||
courier,
|
||||
Private) {
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const getIndexPattern = Private(getIndexPatternProvider);
|
||||
|
||||
class FieldFormatService {
|
||||
constructor() {
|
||||
this.indexPatternIdsByJob = {};
|
||||
this.formatsByJob = {};
|
||||
}
|
||||
|
||||
// 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
|
||||
// attribute set in each pattern which will be compared to the index pattern
|
||||
// configured in the datafeed of each job.
|
||||
// Builds a map of Kibana FieldFormats (ui/field_formats/field_format.js)
|
||||
// against detector index by job ID.
|
||||
populateFormats(jobIds, indexPatterns) {
|
||||
return $q((resolve, reject) => {
|
||||
// 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.
|
||||
// If a Kibana index pattern has not been created
|
||||
// for this index, then no custom field formatting will occur.
|
||||
_.each(jobIds, (jobId) => {
|
||||
const jobObj = mlJobService.getJob(jobId);
|
||||
const datafeedIndices = jobObj.datafeed_config.indices;
|
||||
const indexPattern = _.find(indexPatterns, (index) => {
|
||||
return _.find(datafeedIndices, (datafeedIndex) => {
|
||||
return index.get('title') === datafeedIndex;
|
||||
});
|
||||
});
|
||||
|
||||
// Check if index pattern has been configured to match the index in datafeed.
|
||||
if (indexPattern !== undefined) {
|
||||
this.indexPatternIdsByJob[jobId] = indexPattern.id;
|
||||
}
|
||||
});
|
||||
|
||||
const promises = jobIds.map(jobId => $q.all([
|
||||
this.getFormatsForJob(jobId)
|
||||
]));
|
||||
|
||||
$q.all(promises).then((fmtsByJobByDetector) => {
|
||||
_.each(fmtsByJobByDetector, (formatsByDetector, index) => {
|
||||
this.formatsByJob[jobIds[index]] = formatsByDetector[0];
|
||||
});
|
||||
|
||||
resolve(this.formatsByJob);
|
||||
}).catch(err => {
|
||||
console.log('fieldFormatService error populating formats:', err);
|
||||
reject({ formats: {}, err });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Return the FieldFormat to use for formatting values from
|
||||
// the detector from the job with the specified ID.
|
||||
getFieldFormat(jobId, detectorIndex) {
|
||||
return _.get(this.formatsByJob, [jobId, detectorIndex]);
|
||||
}
|
||||
|
||||
|
||||
// Utility for returning the FieldFormat from a full populated Kibana index pattern object
|
||||
// containing the list of fields by name with their formats.
|
||||
getFieldFormatFromIndexPattern(fullIndexPattern, fieldName, esAggName) {
|
||||
// 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.
|
||||
let fieldFormat = undefined;
|
||||
if (esAggName !== 'cardinality') {
|
||||
const indexPatternFields = _.get(fullIndexPattern, 'fields.byName', []);
|
||||
fieldFormat = _.get(indexPatternFields, [fieldName, 'format']);
|
||||
}
|
||||
|
||||
return fieldFormat;
|
||||
}
|
||||
|
||||
getFormatsForJob(jobId) {
|
||||
return $q((resolve, reject) => {
|
||||
|
||||
const jobObj = mlJobService.getJob(jobId);
|
||||
const detectors = jobObj.analysis_config.detectors || [];
|
||||
const formatsByDetector = {};
|
||||
|
||||
const indexPatternId = this.indexPatternIdsByJob[jobId];
|
||||
if (indexPatternId !== undefined) {
|
||||
// Load the full index pattern configuration to obtain the formats of each field.
|
||||
getIndexPattern(indexPatternId)
|
||||
.then((indexPatternData) => {
|
||||
// Store the FieldFormat for each job by detector_index.
|
||||
const fieldsByName = _.get(indexPatternData, 'fields.byName', []);
|
||||
_.each(detectors, (dtr) => {
|
||||
const esAgg = mlFunctionToESAggregation(dtr.function);
|
||||
// distinct_count detectors should fall back to the default
|
||||
// formatter as the values are just counts.
|
||||
if (dtr.field_name !== undefined && esAgg !== 'cardinality') {
|
||||
formatsByDetector[dtr.detector_index] = _.get(fieldsByName, [dtr.field_name, 'format']);
|
||||
}
|
||||
});
|
||||
|
||||
resolve(formatsByDetector);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(formatsByDetector);
|
||||
}
|
||||
});
|
||||
}
|
||||
class FieldFormatService {
|
||||
constructor() {
|
||||
this.indexPatternIdsByJob = {};
|
||||
this.formatsByJob = {};
|
||||
}
|
||||
|
||||
return new FieldFormatService();
|
||||
// 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
|
||||
// attribute set in each pattern which will be compared to the index pattern
|
||||
// configured in the datafeed of each job.
|
||||
// Builds a map of Kibana FieldFormats (ui/field_formats/field_format.js)
|
||||
// against detector index by job ID.
|
||||
populateFormats(jobIds, indexPatterns) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 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.
|
||||
// If a Kibana index pattern has not been created
|
||||
// for this index, then no custom field formatting will occur.
|
||||
_.each(jobIds, (jobId) => {
|
||||
const jobObj = mlJobService.getJob(jobId);
|
||||
const datafeedIndices = jobObj.datafeed_config.indices;
|
||||
const indexPattern = _.find(indexPatterns, (index) => {
|
||||
return _.find(datafeedIndices, (datafeedIndex) => {
|
||||
return index.get('title') === datafeedIndex;
|
||||
});
|
||||
});
|
||||
|
||||
// Check if index pattern has been configured to match the index in datafeed.
|
||||
if (indexPattern !== undefined) {
|
||||
this.indexPatternIdsByJob[jobId] = indexPattern.id;
|
||||
}
|
||||
});
|
||||
|
||||
const promises = jobIds.map(jobId => Promise.all([
|
||||
this.getFormatsForJob(jobId)
|
||||
]));
|
||||
|
||||
Promise.all(promises).then((fmtsByJobByDetector) => {
|
||||
_.each(fmtsByJobByDetector, (formatsByDetector, index) => {
|
||||
this.formatsByJob[jobIds[index]] = formatsByDetector[0];
|
||||
});
|
||||
|
||||
resolve(this.formatsByJob);
|
||||
}).catch(err => {
|
||||
console.log('fieldFormatService error populating formats:', err);
|
||||
reject({ formats: {}, err });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Return the FieldFormat to use for formatting values from
|
||||
// the detector from the job with the specified ID.
|
||||
getFieldFormat(jobId, detectorIndex) {
|
||||
return _.get(this.formatsByJob, [jobId, detectorIndex]);
|
||||
}
|
||||
|
||||
|
||||
// Utility for returning the FieldFormat from a full populated Kibana index pattern object
|
||||
// containing the list of fields by name with their formats.
|
||||
getFieldFormatFromIndexPattern(fullIndexPattern, fieldName, esAggName) {
|
||||
// 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.
|
||||
let fieldFormat = undefined;
|
||||
if (esAggName !== 'cardinality') {
|
||||
const indexPatternFields = _.get(fullIndexPattern, 'fields.byName', []);
|
||||
fieldFormat = _.get(indexPatternFields, [fieldName, 'format']);
|
||||
}
|
||||
|
||||
return fieldFormat;
|
||||
}
|
||||
|
||||
getFormatsForJob(jobId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const jobObj = mlJobService.getJob(jobId);
|
||||
const detectors = jobObj.analysis_config.detectors || [];
|
||||
const formatsByDetector = {};
|
||||
|
||||
const indexPatternId = this.indexPatternIdsByJob[jobId];
|
||||
if (indexPatternId !== undefined) {
|
||||
// Load the full index pattern configuration to obtain the formats of each field.
|
||||
getIndexPatternById(indexPatternId)
|
||||
.then((indexPatternData) => {
|
||||
// Store the FieldFormat for each job by detector_index.
|
||||
const fieldsByName = _.get(indexPatternData, 'fields.byName', []);
|
||||
_.each(detectors, (dtr) => {
|
||||
const esAgg = mlFunctionToESAggregation(dtr.function);
|
||||
// distinct_count detectors should fall back to the default
|
||||
// formatter as the values are just counts.
|
||||
if (dtr.field_name !== undefined && esAgg !== 'cardinality') {
|
||||
formatsByDetector[dtr.detector_index] = _.get(fieldsByName, [dtr.field_name, 'format']);
|
||||
}
|
||||
});
|
||||
|
||||
resolve(formatsByDetector);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(formatsByDetector);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const mlFieldFormatService = new FieldFormatService();
|
||||
|
||||
|
|
|
@ -13,372 +13,371 @@ import _ from 'lodash';
|
|||
import { ML_RESULTS_INDEX_PATTERN } from 'plugins/ml/constants/index_patterns';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
export function ForecastServiceProvider(es, $q) {
|
||||
|
||||
// Gets a basic summary of the most recently run forecasts for the specified
|
||||
// job, with results at or later than the supplied timestamp.
|
||||
// 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
|
||||
// containing id, earliest and latest keys.
|
||||
function getForecastsSummary(
|
||||
job,
|
||||
query,
|
||||
earliestMs,
|
||||
maxResults
|
||||
) {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
forecasts: []
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, result type and earliest time, plus
|
||||
// the additional query if supplied.
|
||||
const filterCriteria = [
|
||||
{
|
||||
term: { result_type: 'model_forecast_request_stats' }
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: earliestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
// Gets a basic summary of the most recently run forecasts for the specified
|
||||
// job, with results at or later than the supplied timestamp.
|
||||
// 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
|
||||
// containing id, earliest and latest keys.
|
||||
function getForecastsSummary(
|
||||
job,
|
||||
query,
|
||||
earliestMs,
|
||||
maxResults
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
forecasts: []
|
||||
};
|
||||
|
||||
if (query) {
|
||||
filterCriteria.push(query);
|
||||
}
|
||||
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: maxResults,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
},
|
||||
sort: [
|
||||
{ forecast_create_timestamp: { 'order': 'desc' } }
|
||||
]
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total !== 0) {
|
||||
obj.forecasts = resp.hits.hits.map(hit => hit._source);
|
||||
}
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Obtains the earliest and latest timestamps for the forecast data from
|
||||
// the forecast with the specified ID.
|
||||
// Returned response contains earliest and latest properties which are the
|
||||
// timestamps of the first and last model_forecast results.
|
||||
function getForecastDateRange(job, forecastId) {
|
||||
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
earliest: null,
|
||||
latest: null
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, forecast ID, result type and time range.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, result type and earliest time, plus
|
||||
// the additional query if supplied.
|
||||
const filterCriteria = [
|
||||
{
|
||||
term: { result_type: 'model_forecast_request_stats' }
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
}];
|
||||
|
||||
// TODO - add in criteria for detector index and entity fields (by, over, partition)
|
||||
// once forecasting with these parameters is supported.
|
||||
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
earliest: {
|
||||
min: {
|
||||
field: 'timestamp'
|
||||
}
|
||||
},
|
||||
latest: {
|
||||
max: {
|
||||
field: 'timestamp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
obj.earliest = _.get(resp, 'aggregations.earliest.value', null);
|
||||
obj.latest = _.get(resp, 'aggregations.latest.value', null);
|
||||
if (obj.earliest === null || obj.latest === null) {
|
||||
reject(resp);
|
||||
} else {
|
||||
resolve(obj);
|
||||
}
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Obtains the requested forecast model data for the forecast with the specified ID.
|
||||
function getForecastData(
|
||||
job,
|
||||
detectorIndex,
|
||||
forecastId,
|
||||
entityFields,
|
||||
earliestMs,
|
||||
latestMs,
|
||||
interval,
|
||||
aggType) {
|
||||
// Extract the partition, by, over fields on which to filter.
|
||||
const criteriaFields = [];
|
||||
const detector = job.analysis_config.detectors[detectorIndex];
|
||||
if (_.has(detector, 'partition_field_name')) {
|
||||
const partitionEntity = _.find(entityFields, { 'fieldName': detector.partition_field_name });
|
||||
if (partitionEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName },
|
||||
{ fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'over_field_name')) {
|
||||
const overEntity = _.find(entityFields, { 'fieldName': detector.over_field_name });
|
||||
if (overEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'over_field_name', fieldValue: overEntity.fieldName },
|
||||
{ fieldName: 'over_field_value', fieldValue: overEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'by_field_name')) {
|
||||
const byEntity = _.find(entityFields, { 'fieldName': detector.by_field_name });
|
||||
if (byEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'by_field_name', fieldValue: byEntity.fieldName },
|
||||
{ fieldName: 'by_field_value', fieldValue: byEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, forecast ID, detector index, result type and time range.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
},
|
||||
{
|
||||
term: { detector_index: detectorIndex }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
];
|
||||
|
||||
if (query) {
|
||||
filterCriteria.push(query);
|
||||
}
|
||||
|
||||
// Add in term queries for each of the specified criteria.
|
||||
_.each(criteriaFields, (criteria) => {
|
||||
filterCriteria.push({
|
||||
term: {
|
||||
[criteria.fieldName]: criteria.fieldValue
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: maxResults,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
});
|
||||
},
|
||||
sort: [
|
||||
{ forecast_create_timestamp: { 'order': 'desc' } }
|
||||
]
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total !== 0) {
|
||||
obj.forecasts = resp.hits.hits.map(hit => hit._source);
|
||||
}
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Obtains the earliest and latest timestamps for the forecast data from
|
||||
// the forecast with the specified ID.
|
||||
// Returned response contains earliest and latest properties which are the
|
||||
// timestamps of the first and last model_forecast results.
|
||||
function getForecastDateRange(job, forecastId) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
earliest: null,
|
||||
latest: null
|
||||
};
|
||||
|
||||
// If an aggType object has been passed in, use it.
|
||||
// Otherwise default to avg, min and max aggs for the
|
||||
// forecast prediction, upper and lower
|
||||
const forecastAggs = (aggType === undefined) ?
|
||||
{ avg: 'avg', max: 'max', min: 'min' } :
|
||||
{
|
||||
avg: aggType.avg,
|
||||
max: aggType.max,
|
||||
min: aggType.min
|
||||
};
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, forecast ID, result type and time range.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
}];
|
||||
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
// TODO - add in criteria for detector index and entity fields (by, over, partition)
|
||||
// once forecasting with these parameters is supported.
|
||||
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
earliest: {
|
||||
min: {
|
||||
field: 'timestamp'
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
times: {
|
||||
date_histogram: {
|
||||
field: 'timestamp',
|
||||
interval: interval,
|
||||
min_doc_count: 1
|
||||
latest: {
|
||||
max: {
|
||||
field: 'timestamp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
obj.earliest = _.get(resp, 'aggregations.earliest.value', null);
|
||||
obj.latest = _.get(resp, 'aggregations.latest.value', null);
|
||||
if (obj.earliest === null || obj.latest === null) {
|
||||
reject(resp);
|
||||
} else {
|
||||
resolve(obj);
|
||||
}
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Obtains the requested forecast model data for the forecast with the specified ID.
|
||||
function getForecastData(
|
||||
job,
|
||||
detectorIndex,
|
||||
forecastId,
|
||||
entityFields,
|
||||
earliestMs,
|
||||
latestMs,
|
||||
interval,
|
||||
aggType) {
|
||||
// Extract the partition, by, over fields on which to filter.
|
||||
const criteriaFields = [];
|
||||
const detector = job.analysis_config.detectors[detectorIndex];
|
||||
if (_.has(detector, 'partition_field_name')) {
|
||||
const partitionEntity = _.find(entityFields, { 'fieldName': detector.partition_field_name });
|
||||
if (partitionEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName },
|
||||
{ fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'over_field_name')) {
|
||||
const overEntity = _.find(entityFields, { 'fieldName': detector.over_field_name });
|
||||
if (overEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'over_field_name', fieldValue: overEntity.fieldName },
|
||||
{ fieldName: 'over_field_value', fieldValue: overEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'by_field_name')) {
|
||||
const byEntity = _.find(entityFields, { 'fieldName': detector.by_field_name });
|
||||
if (byEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'by_field_name', fieldValue: byEntity.fieldName },
|
||||
{ fieldName: 'by_field_value', fieldValue: byEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, forecast ID, detector index, result type and time range.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
},
|
||||
{
|
||||
term: { detector_index: detectorIndex }
|
||||
},
|
||||
{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
// Add in term queries for each of the specified criteria.
|
||||
_.each(criteriaFields, (criteria) => {
|
||||
filterCriteria.push({
|
||||
term: {
|
||||
[criteria.fieldName]: criteria.fieldValue
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// If an aggType object has been passed in, use it.
|
||||
// Otherwise default to avg, min and max aggs for the
|
||||
// forecast prediction, upper and lower
|
||||
const forecastAggs = (aggType === undefined) ?
|
||||
{ avg: 'avg', max: 'max', min: 'min' } :
|
||||
{
|
||||
avg: aggType.avg,
|
||||
max: aggType.max,
|
||||
min: aggType.min
|
||||
};
|
||||
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
times: {
|
||||
date_histogram: {
|
||||
field: 'timestamp',
|
||||
interval: interval,
|
||||
min_doc_count: 1
|
||||
},
|
||||
aggs: {
|
||||
prediction: {
|
||||
[forecastAggs.avg]: {
|
||||
field: 'forecast_prediction'
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
prediction: {
|
||||
[forecastAggs.avg]: {
|
||||
field: 'forecast_prediction'
|
||||
}
|
||||
},
|
||||
forecastUpper: {
|
||||
[forecastAggs.max]: {
|
||||
field: 'forecast_upper'
|
||||
}
|
||||
},
|
||||
forecastLower: {
|
||||
[forecastAggs.min]: {
|
||||
field: 'forecast_lower'
|
||||
}
|
||||
forecastUpper: {
|
||||
[forecastAggs.max]: {
|
||||
field: 'forecast_upper'
|
||||
}
|
||||
},
|
||||
forecastLower: {
|
||||
[forecastAggs.min]: {
|
||||
field: 'forecast_lower'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []);
|
||||
_.each(aggregationsByTime, (dataForTime) => {
|
||||
const time = dataForTime.key;
|
||||
obj.results[time] = {
|
||||
prediction: _.get(dataForTime, ['prediction', 'value']),
|
||||
forecastUpper: _.get(dataForTime, ['forecastUpper', 'value']),
|
||||
forecastLower: _.get(dataForTime, ['forecastLower', 'value'])
|
||||
};
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []);
|
||||
_.each(aggregationsByTime, (dataForTime) => {
|
||||
const time = dataForTime.key;
|
||||
obj.results[time] = {
|
||||
prediction: _.get(dataForTime, ['prediction', 'value']),
|
||||
forecastUpper: _.get(dataForTime, ['forecastUpper', 'value']),
|
||||
forecastLower: _.get(dataForTime, ['forecastLower', 'value'])
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Runs a forecast
|
||||
function runForecast(jobId, duration) {
|
||||
console.log('ML forecast service run forecast with duration:', duration);
|
||||
return $q((resolve, reject) => {
|
||||
|
||||
ml.forecast({
|
||||
jobId,
|
||||
duration
|
||||
resolve(obj);
|
||||
})
|
||||
.then((resp) => {
|
||||
resolve(resp);
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Gets stats for a forecast that has been run on the specified job.
|
||||
// Returned response contains a stats property, including
|
||||
// forecast_progress (a value from 0 to 1),
|
||||
// and forecast_status ('finished' when complete) properties.
|
||||
function getForecastRequestStats(job, forecastId) {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
stats: {}
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, result type and earliest time.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast_request_stats',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
}];
|
||||
|
||||
es.search({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 1,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total !== 0) {
|
||||
obj.stats = _.first(resp.hits.hits)._source;
|
||||
}
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getForecastsSummary,
|
||||
getForecastDateRange,
|
||||
getForecastData,
|
||||
runForecast,
|
||||
getForecastRequestStats
|
||||
};
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Runs a forecast
|
||||
function runForecast(jobId, duration) {
|
||||
console.log('ML forecast service run forecast with duration:', duration);
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
ml.forecast({
|
||||
jobId,
|
||||
duration
|
||||
})
|
||||
.then((resp) => {
|
||||
resolve(resp);
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Gets stats for a forecast that has been run on the specified job.
|
||||
// Returned response contains a stats property, including
|
||||
// forecast_progress (a value from 0 to 1),
|
||||
// and forecast_status ('finished' when complete) properties.
|
||||
function getForecastRequestStats(job, forecastId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
stats: {}
|
||||
};
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the job ID, result type and earliest time.
|
||||
const filterCriteria = [{
|
||||
query_string: {
|
||||
query: 'result_type:model_forecast_request_stats',
|
||||
analyze_wildcard: true
|
||||
}
|
||||
},
|
||||
{
|
||||
term: { job_id: job.job_id }
|
||||
},
|
||||
{
|
||||
term: { forecast_id: forecastId }
|
||||
}];
|
||||
|
||||
ml.esSearch({
|
||||
index: ML_RESULTS_INDEX_PATTERN,
|
||||
size: 1,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: filterCriteria
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.hits.total !== 0) {
|
||||
obj.stats = _.first(resp.hits.hits)._source;
|
||||
}
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export const mlForecastService = {
|
||||
getForecastsSummary,
|
||||
getForecastDateRange,
|
||||
getForecastData,
|
||||
runForecast,
|
||||
getForecastRequestStats
|
||||
};
|
||||
|
||||
|
|
|
@ -39,7 +39,11 @@ export function http(options) {
|
|||
|
||||
fetch(url, payload)
|
||||
.then((resp) => {
|
||||
resolve(resp.json());
|
||||
if (resp.ok === true) {
|
||||
resolve(resp.json());
|
||||
} else {
|
||||
reject(resp);
|
||||
}
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
|
|
|
@ -11,160 +11,160 @@
|
|||
// 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';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
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 = {
|
||||
// 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 new Promise((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'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ml.esSearch({
|
||||
index: ML_NOTIFICATION_INDEX_PATTERN,
|
||||
ignore_unavailable: true,
|
||||
size: 1000,
|
||||
body:
|
||||
{
|
||||
sort: [
|
||||
{ timestamp: { order: 'asc' } },
|
||||
{ job_id: { order: 'asc' } }
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
should: [
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
job_id: '' // catch system messages
|
||||
bool: {
|
||||
must_not: {
|
||||
term: {
|
||||
level: 'activity'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
term: {
|
||||
job_id: jobId // messages for specified jobId
|
||||
}
|
||||
}
|
||||
jobFilter,
|
||||
timeFilter
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 messages = [];
|
||||
if (resp.hits.total !== 0) {
|
||||
messages = resp.hits.hits.map(hit => hit._source);
|
||||
}
|
||||
resolve({ messages });
|
||||
})
|
||||
.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
|
||||
};
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// search highest, most recent audit messages for all jobs for the last 24hrs.
|
||||
function getAuditMessagesSummary() {
|
||||
return new Promise((resolve, reject) => {
|
||||
ml.esSearch({
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const jobMessagesService = {
|
||||
getJobAuditMessages,
|
||||
getAuditMessagesSummary
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -394,5 +394,13 @@ export const ml = {
|
|||
method: 'POST',
|
||||
data
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
esSearch(obj) {
|
||||
return http({
|
||||
url: `${basePath}/es_search`,
|
||||
method: 'POST',
|
||||
data: obj
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@ import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/che
|
|||
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 { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
import template from './calendars_list.html';
|
||||
|
||||
|
@ -27,7 +28,8 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
mlNodeCount: getMlNodeCount
|
||||
mlNodeCount: getMlNodeCount,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -40,9 +42,7 @@ module.controller('MlCalendarsList',
|
|||
$filter,
|
||||
$route,
|
||||
$location,
|
||||
$q,
|
||||
pagerFactory,
|
||||
Private,
|
||||
timefilter,
|
||||
mlConfirmModalService) {
|
||||
|
||||
|
@ -117,7 +117,7 @@ module.controller('MlCalendarsList',
|
|||
title: `Delete calendar`
|
||||
})
|
||||
.then(() => {
|
||||
$q.when(ml.deleteCalendar({ calendarId }))
|
||||
ml.deleteCalendar({ calendarId })
|
||||
.then(loadCalendars)
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
|
@ -127,7 +127,7 @@ module.controller('MlCalendarsList',
|
|||
};
|
||||
|
||||
function loadCalendars() {
|
||||
$q.when(ml.calendars())
|
||||
ml.calendars()
|
||||
.then((resp) => {
|
||||
calendars = resp;
|
||||
$scope.pager = pagerFactory.create(calendars.length, PAGE_SIZE, 1);
|
||||
|
|
|
@ -17,7 +17,6 @@ const module = uiModules.get('apps/ml');
|
|||
|
||||
module.controller('MlImportEventsModal', function (
|
||||
$scope,
|
||||
$q,
|
||||
$timeout,
|
||||
$modalInstance) {
|
||||
|
||||
|
@ -67,7 +66,7 @@ module.controller('MlImportEventsModal', function (
|
|||
};
|
||||
|
||||
function readFile(file) {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$scope.loadingLock = true;
|
||||
|
||||
if (file && file.size) {
|
||||
|
|
|
@ -11,10 +11,10 @@ import template from './import_events_modal.html';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlImportEventsService', function ($q, $modal) {
|
||||
module.service('mlImportEventsService', function ($modal) {
|
||||
|
||||
this.openImportEventsWindow = function () {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const modal = $modal.open({
|
||||
template,
|
||||
controller: 'MlImportEventsModal',
|
||||
|
|
|
@ -11,10 +11,10 @@ import template from './new_event_modal.html';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlNewEventService', function ($q, $modal) {
|
||||
module.service('mlNewEventService', function ($modal) {
|
||||
|
||||
this.openNewEventWindow = function () {
|
||||
return $q((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const modal = $modal.open({
|
||||
template,
|
||||
controller: 'MlNewEventModal',
|
||||
|
|
|
@ -18,9 +18,10 @@ import uiRoutes from 'ui/routes';
|
|||
import { checkLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
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 { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlCalendarService } from 'plugins/ml/services/calendar_service';
|
||||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
|
||||
import template from './create_calendar.html';
|
||||
|
@ -31,7 +32,8 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
checkMlNodesAvailable
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
})
|
||||
.when('/settings/calendars_list/edit_calendar/:calendarId', {
|
||||
|
@ -39,7 +41,8 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
checkMlNodesAvailable
|
||||
checkMlNodesAvailable,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -50,14 +53,9 @@ module.controller('MlCreateCalendar',
|
|||
function (
|
||||
$scope,
|
||||
$route,
|
||||
$location,
|
||||
$q,
|
||||
timefilter,
|
||||
Private) {
|
||||
$location) {
|
||||
const msgs = mlMessageBarService;
|
||||
msgs.clear();
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlCalendarService = Private(CalendarServiceProvider);
|
||||
|
||||
const calendarId = $route.current.params.calendarId;
|
||||
$scope.isNewCalendar = (calendarId === undefined);
|
||||
|
@ -142,7 +140,7 @@ module.controller('MlCreateCalendar',
|
|||
if (validateCalendarId(calendar.calendarId, $scope.validation.checks)) {
|
||||
$scope.saveLock = true;
|
||||
const saveFunc = $scope.isNewCalendar ? (c => ml.addCalendar(c)) : (c => ml.updateCalendar(c));
|
||||
$q.when(saveFunc(calendar))
|
||||
saveFunc(calendar)
|
||||
.then(() => {
|
||||
$location.path('settings/calendars_list');
|
||||
$scope.saveLock = false;
|
||||
|
|
|
@ -10,6 +10,7 @@ import uiRoutes from 'ui/routes';
|
|||
import { checkLicense } from 'plugins/ml/license/check_license';
|
||||
import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege';
|
||||
import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
import template from './settings.html';
|
||||
|
||||
|
@ -19,7 +20,8 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
mlNodeCount: getMlNodeCount
|
||||
mlNodeCount: getMlNodeCount,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ import { parseInterval } from '../../../common/util/parse_interval';
|
|||
import { Modal } from './modal';
|
||||
import { PROGRESS_STATES } from './progress_states';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlForecastService } from 'plugins/ml/services/forecast_service';
|
||||
|
||||
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.
|
||||
|
@ -123,7 +125,7 @@ class ForecastingModal extends Component {
|
|||
jobOpeningState: PROGRESS_STATES.WAITING
|
||||
});
|
||||
|
||||
this.props.mlJobService.openJob(this.props.job.job_id)
|
||||
mlJobService.openJob(this.props.job.job_id)
|
||||
.then(() => {
|
||||
// If open was successful run the forecast, then close the job again.
|
||||
this.setState({
|
||||
|
@ -160,7 +162,7 @@ class ForecastingModal extends Component {
|
|||
// formats accepted by Kibana (w, M, y) are not valid formats in Elasticsearch.
|
||||
const durationInSeconds = parseInterval(this.state.newForecastDuration).asSeconds();
|
||||
|
||||
this.props.mlForecastService.runForecast(this.props.job.job_id, `${durationInSeconds}s`)
|
||||
mlForecastService.runForecast(this.props.job.job_id, `${durationInSeconds}s`)
|
||||
.then((resp) => {
|
||||
// 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.
|
||||
|
@ -180,7 +182,7 @@ class ForecastingModal extends Component {
|
|||
let previousProgress = 0;
|
||||
let noProgressMs = 0;
|
||||
this.forecastChecker = setInterval(() => {
|
||||
this.props.mlForecastService.getForecastRequestStats(this.props.job, forecastId)
|
||||
mlForecastService.getForecastRequestStats(this.props.job, forecastId)
|
||||
.then((resp) => {
|
||||
// Get the progress (stats value is between 0 and 1).
|
||||
const progress = _.get(resp, ['stats', 'forecast_progress'], previousProgress);
|
||||
|
@ -198,7 +200,7 @@ class ForecastingModal extends Component {
|
|||
|
||||
if (closeJobAfterRunning === true) {
|
||||
this.setState({ jobClosingState: PROGRESS_STATES.WAITING });
|
||||
this.props.mlJobService.closeJob(this.props.job.job_id)
|
||||
mlJobService.closeJob(this.props.job.job_id)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
jobClosingState: PROGRESS_STATES.DONE
|
||||
|
@ -263,7 +265,7 @@ class ForecastingModal extends Component {
|
|||
forecast_status: FORECAST_REQUEST_STATE.FINISHED
|
||||
}
|
||||
};
|
||||
this.props.mlForecastService.getForecastsSummary(
|
||||
mlForecastService.getForecastsSummary(
|
||||
job,
|
||||
statusFinishedQuery,
|
||||
bounds.min.valueOf(),
|
||||
|
@ -399,8 +401,6 @@ ForecastingModal.propTypes = {
|
|||
job: PropTypes.object,
|
||||
detectorIndex: PropTypes.number,
|
||||
entities: PropTypes.array,
|
||||
mlForecastService: PropTypes.object.isRequired,
|
||||
mlJobService: PropTypes.object.isRequired,
|
||||
loadForForecastId: PropTypes.func,
|
||||
};
|
||||
|
||||
|
|
|
@ -10,24 +10,15 @@ import 'ngreact';
|
|||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml', ['react']);
|
||||
|
||||
import { JobServiceProvider } from 'plugins/ml/services/job_service';
|
||||
import { ForecastServiceProvider } from 'plugins/ml/services/forecast_service';
|
||||
import { ForecastingModal } from './forecasting_modal';
|
||||
|
||||
module.directive('mlForecastingModal', function ($injector) {
|
||||
const Private = $injector.get('Private');
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlForecastService = Private(ForecastServiceProvider);
|
||||
const timefilter = $injector.get('timefilter');
|
||||
const reactDirective = $injector.get('reactDirective');
|
||||
return reactDirective(
|
||||
ForecastingModal,
|
||||
undefined,
|
||||
{ restrict: 'E' },
|
||||
{
|
||||
mlForecastService,
|
||||
mlJobService,
|
||||
timefilter
|
||||
}
|
||||
{ timefilter }
|
||||
);
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ import { TimeBuckets } from 'ui/time_buckets';
|
|||
import ContextChartMask from 'plugins/ml/timeseriesexplorer/context_chart_mask';
|
||||
import { findNearestChartPointToTime } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
|
||||
import { mlEscape } from 'plugins/ml/util/string_utils';
|
||||
import { FieldFormatServiceProvider } from 'plugins/ml/services/field_format_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
@ -46,7 +46,6 @@ module.directive('mlTimeseriesChart', function (
|
|||
|
||||
function link(scope, element) {
|
||||
|
||||
const mlFieldFormatService = Private(FieldFormatServiceProvider);
|
||||
// Key dimensions for the viz and constituent charts.
|
||||
let svgWidth = angular.element('.results-container').width();
|
||||
const focusZoomPanelHeight = 25;
|
||||
|
|
|
@ -11,149 +11,142 @@ import _ from 'lodash';
|
|||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { isModelPlotEnabled } from 'plugins/ml/../common/util/job_utils';
|
||||
import { buildConfigFromDetector } from 'plugins/ml/util/chart_config_builder';
|
||||
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/ml');
|
||||
|
||||
module.service('mlTimeSeriesSearchService', function (
|
||||
$q,
|
||||
Private) {
|
||||
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
|
||||
this.getMetricData = function (job, detectorIndex, entityFields, earliestMs, latestMs, interval) {
|
||||
if (isModelPlotEnabled(job, detectorIndex, entityFields)) {
|
||||
// Extract the partition, by, over fields on which to filter.
|
||||
const criteriaFields = [];
|
||||
const detector = job.analysis_config.detectors[detectorIndex];
|
||||
if (_.has(detector, 'partition_field_name')) {
|
||||
const partitionEntity = _.find(entityFields, { 'fieldName': detector.partition_field_name });
|
||||
if (partitionEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName },
|
||||
{ fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue });
|
||||
}
|
||||
function getMetricData(job, detectorIndex, entityFields, earliestMs, latestMs, interval) {
|
||||
if (isModelPlotEnabled(job, detectorIndex, entityFields)) {
|
||||
// Extract the partition, by, over fields on which to filter.
|
||||
const criteriaFields = [];
|
||||
const detector = job.analysis_config.detectors[detectorIndex];
|
||||
if (_.has(detector, 'partition_field_name')) {
|
||||
const partitionEntity = _.find(entityFields, { 'fieldName': detector.partition_field_name });
|
||||
if (partitionEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName },
|
||||
{ fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'over_field_name')) {
|
||||
const overEntity = _.find(entityFields, { 'fieldName': detector.over_field_name });
|
||||
if (overEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'over_field_name', fieldValue: overEntity.fieldName },
|
||||
{ fieldName: 'over_field_value', fieldValue: overEntity.fieldValue });
|
||||
}
|
||||
if (_.has(detector, 'over_field_name')) {
|
||||
const overEntity = _.find(entityFields, { 'fieldName': detector.over_field_name });
|
||||
if (overEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'over_field_name', fieldValue: overEntity.fieldName },
|
||||
{ fieldName: 'over_field_value', fieldValue: overEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (_.has(detector, 'by_field_name')) {
|
||||
const byEntity = _.find(entityFields, { 'fieldName': detector.by_field_name });
|
||||
if (byEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'by_field_name', fieldValue: byEntity.fieldName },
|
||||
{ fieldName: 'by_field_value', fieldValue: byEntity.fieldValue });
|
||||
}
|
||||
if (_.has(detector, 'by_field_name')) {
|
||||
const byEntity = _.find(entityFields, { 'fieldName': detector.by_field_name });
|
||||
if (byEntity !== undefined) {
|
||||
criteriaFields.push(
|
||||
{ fieldName: 'by_field_name', fieldValue: byEntity.fieldName },
|
||||
{ fieldName: 'by_field_value', fieldValue: byEntity.fieldValue });
|
||||
}
|
||||
}
|
||||
|
||||
return mlResultsService.getModelPlotOutput(
|
||||
job.job_id,
|
||||
detectorIndex,
|
||||
criteriaFields,
|
||||
return mlResultsService.getModelPlotOutput(
|
||||
job.job_id,
|
||||
detectorIndex,
|
||||
criteriaFields,
|
||||
earliestMs,
|
||||
latestMs,
|
||||
interval
|
||||
);
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
const chartConfig = buildConfigFromDetector(job, detectorIndex);
|
||||
|
||||
mlResultsService.getMetricData(
|
||||
chartConfig.datafeedConfig.indices,
|
||||
chartConfig.datafeedConfig.types,
|
||||
entityFields,
|
||||
chartConfig.datafeedConfig.query,
|
||||
chartConfig.metricFunction,
|
||||
chartConfig.metricFieldName,
|
||||
chartConfig.timeField,
|
||||
earliestMs,
|
||||
latestMs,
|
||||
interval
|
||||
);
|
||||
} else {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = {
|
||||
success: true,
|
||||
results: {}
|
||||
};
|
||||
|
||||
const chartConfig = buildConfigFromDetector(job, detectorIndex);
|
||||
|
||||
mlResultsService.getMetricData(
|
||||
chartConfig.datafeedConfig.indices,
|
||||
chartConfig.datafeedConfig.types,
|
||||
entityFields,
|
||||
chartConfig.datafeedConfig.query,
|
||||
chartConfig.metricFunction,
|
||||
chartConfig.metricFieldName,
|
||||
chartConfig.timeField,
|
||||
earliestMs,
|
||||
latestMs,
|
||||
interval
|
||||
)
|
||||
.then((resp) => {
|
||||
_.each(resp.results, (value, time) => {
|
||||
obj.results[time] = {
|
||||
'actual': value
|
||||
};
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
)
|
||||
.then((resp) => {
|
||||
_.each(resp.results, (value, time) => {
|
||||
obj.results[time] = {
|
||||
'actual': value
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Builds chart detail information (charting function description and entity counts) used
|
||||
// in the title area of the time series chart.
|
||||
// Queries Elasticsearch if necessary to obtain the distinct count of entities
|
||||
// for which data is being plotted.
|
||||
this.getChartDetails = function (job, detectorIndex, entityFields, earliestMs, latestMs) {
|
||||
return $q((resolve, reject) => {
|
||||
const obj = { success: true, results: { functionLabel: '', entityData: { entities: [] } } };
|
||||
|
||||
const chartConfig = buildConfigFromDetector(job, detectorIndex);
|
||||
let functionLabel = chartConfig.metricFunction;
|
||||
if (chartConfig.metricFieldName !== undefined) {
|
||||
functionLabel += ' ';
|
||||
functionLabel += chartConfig.metricFieldName;
|
||||
}
|
||||
obj.results.functionLabel = functionLabel;
|
||||
|
||||
const blankEntityFields = _.filter(entityFields, (entity) => {
|
||||
return entity.fieldValue.length === 0;
|
||||
});
|
||||
|
||||
// Look to see if any of the entity fields have defined values
|
||||
// (i.e. blank input), and if so obtain the cardinality.
|
||||
if (blankEntityFields.length === 0) {
|
||||
obj.results.entityData.count = 1;
|
||||
obj.results.entityData.entities = entityFields;
|
||||
resolve(obj);
|
||||
} else {
|
||||
const entityFieldNames = _.map(blankEntityFields, 'fieldName');
|
||||
ml.getCardinalityOfFields({
|
||||
index: chartConfig.datafeedConfig.indices,
|
||||
types: chartConfig.datafeedConfig.types,
|
||||
fieldNames: entityFieldNames,
|
||||
query: chartConfig.datafeedConfig.query,
|
||||
timeFieldName: chartConfig.timeField,
|
||||
earliestMs,
|
||||
latestMs
|
||||
resolve(obj);
|
||||
})
|
||||
.then((results) => {
|
||||
_.each(blankEntityFields, (field) => {
|
||||
obj.results.entityData.entities.push({
|
||||
fieldName: field.fieldName,
|
||||
cardinality: _.get(results, field.fieldName, 0)
|
||||
});
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
}
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
// Builds chart detail information (charting function description and entity counts) used
|
||||
// in the title area of the time series chart.
|
||||
// Queries Elasticsearch if necessary to obtain the distinct count of entities
|
||||
// for which data is being plotted.
|
||||
function getChartDetails(job, detectorIndex, entityFields, earliestMs, latestMs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const obj = { success: true, results: { functionLabel: '', entityData: { entities: [] } } };
|
||||
|
||||
const chartConfig = buildConfigFromDetector(job, detectorIndex);
|
||||
let functionLabel = chartConfig.metricFunction;
|
||||
if (chartConfig.metricFieldName !== undefined) {
|
||||
functionLabel += ' ';
|
||||
functionLabel += chartConfig.metricFieldName;
|
||||
}
|
||||
obj.results.functionLabel = functionLabel;
|
||||
|
||||
const blankEntityFields = _.filter(entityFields, (entity) => {
|
||||
return entity.fieldValue.length === 0;
|
||||
});
|
||||
|
||||
// Look to see if any of the entity fields have defined values
|
||||
// (i.e. blank input), and if so obtain the cardinality.
|
||||
if (blankEntityFields.length === 0) {
|
||||
obj.results.entityData.count = 1;
|
||||
obj.results.entityData.entities = entityFields;
|
||||
resolve(obj);
|
||||
} else {
|
||||
const entityFieldNames = _.map(blankEntityFields, 'fieldName');
|
||||
ml.getCardinalityOfFields({
|
||||
index: chartConfig.datafeedConfig.indices,
|
||||
types: chartConfig.datafeedConfig.types,
|
||||
fieldNames: entityFieldNames,
|
||||
query: chartConfig.datafeedConfig.query,
|
||||
timeFieldName: chartConfig.timeField,
|
||||
earliestMs,
|
||||
latestMs
|
||||
})
|
||||
.then((results) => {
|
||||
_.each(blankEntityFields, (field) => {
|
||||
obj.results.entityData.entities.push({
|
||||
fieldName: field.fieldName,
|
||||
cardinality: _.get(results, field.fieldName, 0)
|
||||
});
|
||||
});
|
||||
|
||||
resolve(obj);
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export const mlTimeSeriesSearchService = {
|
||||
getMetricData,
|
||||
getChartDetails
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
isTimeSeriesViewDetector,
|
||||
isModelPlotEnabled,
|
||||
mlFunctionToESAggregation } from 'plugins/ml/../common/util/job_utils';
|
||||
import { getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import { loadIndexPatterns, getIndexPatterns } from 'plugins/ml/util/index_utils';
|
||||
import {
|
||||
createTimeSeriesJobData,
|
||||
processForecastResults,
|
||||
|
@ -38,13 +38,15 @@ import {
|
|||
processScheduledEventsForChart } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
|
||||
import { refreshIntervalWatcher } from 'plugins/ml/util/refresh_interval_watcher';
|
||||
import { IntervalHelperProvider, getBoundsRoundedToInterval } from 'plugins/ml/util/ml_time_buckets';
|
||||
import { ResultsServiceProvider } from 'plugins/ml/services/results_service';
|
||||
import { mlResultsService } from 'plugins/ml/services/results_service';
|
||||
import template from './timeseriesexplorer.html';
|
||||
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';
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
|
||||
import { JobSelectServiceProvider } from 'plugins/ml/components/job_select_list/job_select_service';
|
||||
import { mlForecastService } from 'plugins/ml/services/forecast_service';
|
||||
import { mlTimeSeriesSearchService } from 'plugins/ml/timeseriesexplorer/timeseries_search_service';
|
||||
import { initPromise } from 'plugins/ml/util/promise';
|
||||
|
||||
uiRoutes
|
||||
.when('/timeseriesexplorer/?', {
|
||||
|
@ -52,8 +54,9 @@ uiRoutes
|
|||
resolve: {
|
||||
CheckLicense: checkLicense,
|
||||
privileges: checkGetJobsPrivilege,
|
||||
indexPatterns: getIndexPatterns,
|
||||
mlNodeCount: getMlNodeCount
|
||||
indexPatterns: loadIndexPatterns,
|
||||
mlNodeCount: getMlNodeCount,
|
||||
initPromise: initPromise(true)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -67,7 +70,6 @@ module.controller('MlTimeSeriesExplorerController', function (
|
|||
Private,
|
||||
timefilter,
|
||||
AppState,
|
||||
mlTimeSeriesSearchService,
|
||||
mlAnomaliesTableService) {
|
||||
|
||||
$scope.timeFieldName = 'timestamp';
|
||||
|
@ -78,10 +80,6 @@ module.controller('MlTimeSeriesExplorerController', function (
|
|||
const ANOMALIES_MAX_RESULTS = 500;
|
||||
const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket.
|
||||
const TimeBuckets = Private(IntervalHelperProvider);
|
||||
const mlResultsService = Private(ResultsServiceProvider);
|
||||
const mlJobService = Private(JobServiceProvider);
|
||||
const mlFieldFormatService = Private(FieldFormatServiceProvider);
|
||||
const mlForecastService = Private(ForecastServiceProvider);
|
||||
const mlJobSelectService = Private(JobSelectServiceProvider);
|
||||
|
||||
$scope.jobPickerSelections = [];
|
||||
|
@ -650,7 +648,7 @@ module.controller('MlTimeSeriesExplorerController', function (
|
|||
updateControlsForDetector();
|
||||
|
||||
// Populate the map of jobs / detectors / field formatters for the selected IDs and refresh.
|
||||
mlFieldFormatService.populateFormats([jobId], $route.current.locals.indexPatterns)
|
||||
mlFieldFormatService.populateFormats([jobId], getIndexPatterns())
|
||||
.finally(() => {
|
||||
// Load the data - if the FieldFormats failed to populate
|
||||
// the default formatting will be used for metric values.
|
||||
|
|
|
@ -9,31 +9,58 @@
|
|||
import { notify } from 'ui/notify';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
export function getIndexPatterns(Private) {
|
||||
let indexPatterns = [];
|
||||
let fullIndexPatterns = [];
|
||||
let currentIndexPattern = null;
|
||||
let currentSavedSearch = null;
|
||||
|
||||
export function loadIndexPatterns(Private, courier) {
|
||||
fullIndexPatterns = courier.indexPatterns;
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
}).then(response => response.savedObjects);
|
||||
}).then((response) => {
|
||||
indexPatterns = response.savedObjects;
|
||||
return indexPatterns;
|
||||
});
|
||||
}
|
||||
|
||||
export function getIndexPattern(courier, indexPatternId) {
|
||||
return courier.indexPatterns.get(indexPatternId);
|
||||
export function getIndexPatterns() {
|
||||
return indexPatterns;
|
||||
}
|
||||
|
||||
export function getIndexPatternWithRoute(courier, $route) {
|
||||
return getIndexPattern(courier, $route.current.params.index);
|
||||
export function getIndexPatternIdFromName(name) {
|
||||
for (let j = 0; j < indexPatterns.length; j++) {
|
||||
if (indexPatterns[j].get('title') === name) {
|
||||
return indexPatterns[j].id;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function getIndexPatternProvider(courier) {
|
||||
return function (indexPatternId) {
|
||||
return getIndexPattern(courier, indexPatternId);
|
||||
};
|
||||
export function loadCurrentIndexPattern(courier, $route) {
|
||||
fullIndexPatterns = courier.indexPatterns;
|
||||
currentIndexPattern = fullIndexPatterns.get($route.current.params.index);
|
||||
return currentIndexPattern;
|
||||
}
|
||||
|
||||
export function getSavedSearchWithRoute(courier, $route, savedSearches) {
|
||||
return savedSearches.get($route.current.params.savedSearchId);
|
||||
export function getIndexPatternById(id) {
|
||||
return fullIndexPatterns.get(id);
|
||||
}
|
||||
|
||||
export function loadCurrentSavedSearch(courier, $route, savedSearches) {
|
||||
currentSavedSearch = savedSearches.get($route.current.params.savedSearchId);
|
||||
return currentSavedSearch;
|
||||
}
|
||||
|
||||
export function getCurrentIndexPattern() {
|
||||
return currentIndexPattern;
|
||||
}
|
||||
|
||||
export function getCurrentSavedSearch() {
|
||||
return currentSavedSearch;
|
||||
}
|
||||
|
||||
// returns true if the index passed in is time based
|
||||
|
|
20
x-pack/plugins/ml/public/util/promise.js
Normal file
20
x-pack/plugins/ml/public/util/promise.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// to work around the need for wrapping all of our promises in $q for our angular code
|
||||
// we replace the native Promise with $q.
|
||||
// For pages which are mainly react, initPromise should be called with false
|
||||
// and any calls to promises in angular code wrapped in $q.when
|
||||
// once we're free of angular this file can be removed.
|
||||
|
||||
const promise = window.Promise;
|
||||
|
||||
export function initPromise(replacePromise) {
|
||||
return function ($q) {
|
||||
window.Promise = replacePromise ? $q : promise;
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
|
@ -110,7 +110,7 @@ export function calculateModelMemoryLimitProvider(callWithRequest) {
|
|||
mmlMB = (maxBytes / numeral('1MB').value());
|
||||
}
|
||||
}
|
||||
response(`${mmlMB}MB`);
|
||||
response({ modelMemoryLimit: `${mmlMB}MB` });
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
|
|
|
@ -73,7 +73,7 @@ export async function validateModelMemoryLimit(callWithRequest, job, duration) {
|
|||
duration.start,
|
||||
duration.end,
|
||||
true);
|
||||
const mmlEstimateBytes = numeral(mmlEstimate).value();
|
||||
const mmlEstimateBytes = numeral(mmlEstimate.modelMemoryLimit).value();
|
||||
|
||||
let runEstimateGreaterThenMml = true;
|
||||
// if max_model_memory_limit has been set,
|
||||
|
|
|
@ -125,4 +125,18 @@ export function systemRoutes(server, commonRouteConfig) {
|
|||
...commonRouteConfig
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/ml/es_search',
|
||||
handler(request, reply) {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
return callWithRequest('search', request.payload)
|
||||
.then(resp => reply(resp))
|
||||
.catch(resp => reply(wrapError(resp)));
|
||||
},
|
||||
config: {
|
||||
...commonRouteConfig
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue