[ML] Injectables refactor (#50512)

* [ML] Injectables refactor

* removing unrelated files

* additional typescript conversion

* more typescript conversion

* adding some return types

* fixing eui errors

* typescripting license checks

* updated based on review

* fixing merge conflict error

* converting tests to jest

* fixing types
This commit is contained in:
James Gowdy 2019-11-18 14:36:07 +00:00 committed by GitHub
parent 2166044671
commit eb4c47ef0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 570 additions and 655 deletions

View file

@ -1,70 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { parseInterval } from '../parse_interval';
import expect from '@kbn/expect';
describe('ML parse interval util', function () {
it('correctly parses an interval containing unit and value', function () {
let duration = parseInterval('1d');
expect(duration.as('d')).to.be(1);
duration = parseInterval('2y');
expect(duration.as('y')).to.be(2);
duration = parseInterval('5M');
expect(duration.as('M')).to.be(5);
duration = parseInterval('5m');
expect(duration.as('m')).to.be(5);
duration = parseInterval('250ms');
expect(duration.as('ms')).to.be(250);
duration = parseInterval('100s');
expect(duration.as('s')).to.be(100);
duration = parseInterval('23d');
expect(duration.as('d')).to.be(23);
duration = parseInterval('52w');
expect(duration.as('w')).to.be(52);
duration = parseInterval('0s');
expect(duration.as('s')).to.be(0);
duration = parseInterval('0h');
expect(duration.as('h')).to.be(0);
});
it('correctly handles zero value intervals', function () {
let duration = parseInterval('0h');
expect(duration.as('h')).to.be(0);
duration = parseInterval('0d');
expect(duration).to.not.be.ok();
});
it('returns null for an invalid interval', function () {
let duration = parseInterval('');
expect(duration).to.not.be.ok();
duration = parseInterval(null);
expect(duration).to.not.be.ok();
duration = parseInterval('234asdf');
expect(duration).to.not.be.ok();
duration = parseInterval('m');
expect(duration).to.not.be.ok();
duration = parseInterval('1.5h');
expect(duration).to.not.be.ok();
});
});

View file

@ -1,6 +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.
*/
export function tabColor(name: string): string;

View file

@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import * as euiVars from '@elastic/eui/dist/eui_theme_dark.json';
import { stringHash } from './string_utils';
import euiVars from '@elastic/eui/dist/eui_theme_dark.json';
const COLORS = [
euiVars.euiColorVis0,
@ -20,18 +18,32 @@ const COLORS = [
euiVars.euiColorVis8,
euiVars.euiColorVis9,
euiVars.euiColorDarkShade,
euiVars.euiColorPrimary
euiVars.euiColorPrimary,
];
const colorMap = {};
const colorMap: Record<string, string> = {};
export function tabColor(name) {
export function tabColor(name: string): string {
if (colorMap[name] === undefined) {
const n = stringHash(name);
const color = COLORS[(n % COLORS.length)];
const color = COLORS[n % COLORS.length];
colorMap[name] = color;
return color;
} else {
return colorMap[name];
}
}
function stringHash(str: string): number {
let hash = 0;
let chr = 0;
if (str.length === 0) {
return hash;
}
for (let i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise
hash |= 0; // eslint-disable-line no-bitwise
}
return hash < 0 ? hash * -2 : hash;
}

View file

@ -33,3 +33,5 @@ export const ML_DATA_PREVIEW_COUNT: number;
export function isJobIdValid(jobId: string): boolean;
export function processCreatedBy(customSettings: { created_by?: string }): void;
export function mlFunctionToESAggregation(functionName: string): string | null;

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { parseInterval } from './parse_interval';
describe('ML parse interval util', () => {
test('correctly parses an interval containing unit and value', () => {
expect(parseInterval('1d')!.as('d')).toBe(1);
expect(parseInterval('2y')!.as('y')).toBe(2);
expect(parseInterval('5M')!.as('M')).toBe(5);
expect(parseInterval('5m')!.as('m')).toBe(5);
expect(parseInterval('250ms')!.as('ms')).toBe(250);
expect(parseInterval('100s')!.as('s')).toBe(100);
expect(parseInterval('23d')!.as('d')).toBe(23);
expect(parseInterval('52w')!.as('w')).toBe(52);
expect(parseInterval('0s')!.as('s')).toBe(0);
expect(parseInterval('0s')!.as('h')).toBe(0);
});
test('correctly handles zero value intervals', () => {
expect(parseInterval('0h')!.as('h')).toBe(0);
expect(parseInterval('0d')).toBe(null);
});
test('returns null for an invalid interval', () => {
expect(parseInterval('')).toBe(null);
expect(parseInterval('234asdf')).toBe(null);
expect(parseInterval('m')).toBe(null);
expect(parseInterval('1.5h')).toBe(null);
});
});

View file

@ -4,17 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment';
import { duration, Duration, unitOfTime } from 'moment';
import dateMath from '@elastic/datemath';
type SupportedUnits = unitOfTime.Base;
// Assume interval is in the form (value)(unit), such as "1h"
const INTERVAL_STRING_RE = new RegExp('^([0-9]*)\\s*(' + dateMath.units.join('|') + ')$');
// moment.js is only designed to allow fractional values between 0 and 1
// for units of hour or less.
const SUPPORT_ZERO_DURATION_UNITS = ['ms', 's', 'm', 'h'];
const SUPPORT_ZERO_DURATION_UNITS: SupportedUnits[] = ['ms', 's', 'm', 'h'];
// Parses an interval String, such as 7d, 1h or 30m to a moment duration.
// Differs from the Kibana ui/utils/parse_interval in the following ways:
@ -25,22 +25,25 @@ const SUPPORT_ZERO_DURATION_UNITS = ['ms', 's', 'm', 'h'];
// to work with units less than 'day'.
// 3. Fractional intervals e.g. 1.5h or 4.5d are not allowed, in line with the behaviour
// of the Elasticsearch date histogram aggregation.
export function parseInterval(interval) {
const matches = String(interval).trim().match(INTERVAL_STRING_RE);
if (!Array.isArray(matches)) return null;
if (matches.length < 3) return null;
export function parseInterval(interval: string): Duration | null {
const matches = String(interval)
.trim()
.match(INTERVAL_STRING_RE);
if (!Array.isArray(matches) || matches.length < 3) {
return null;
}
try {
const value = parseInt(matches[1]);
const unit = matches[2];
const value = parseInt(matches[1], 10);
const unit = matches[2] as SupportedUnits;
// In line with moment.js, only allow zero value intervals when the unit is less than 'day'.
// And check for isNaN as e.g. valueless 'm' will pass the regex test.
if (isNaN(value) || (value < 1 && SUPPORT_ZERO_DURATION_UNITS.indexOf(unit) === -1)) {
if (isNaN(value) || (value < 1 && SUPPORT_ZERO_DURATION_UNITS.indexOf(unit) === -1)) {
return null;
}
return moment.duration(value, unit);
return duration(value, unit);
} catch (e) {
return null;
}

View file

@ -1,8 +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.
*/
export function renderTemplate(str: string, data: string): string;
export function stringHash(str: string): string;

View file

@ -4,29 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { renderTemplate } from '../string_utils';
import { renderTemplate } from './string_utils';
describe('ML - string utils', () => {
describe('renderTemplate', () => {
it('returns plain string', () => {
test('returns plain string', () => {
const templateString = 'plain string';
const result = renderTemplate(templateString);
expect(result).to.be(result);
expect(result).toBe(result);
});
it('returns rendered template with one replacement', () => {
test('returns rendered template with one replacement', () => {
const templateString = 'string with {{one}} replacement';
const result = renderTemplate(templateString, { one: '1' });
expect(result).to.be('string with 1 replacement');
expect(result).toBe('string with 1 replacement');
});
it('returns rendered template with two replacements', () => {
test('returns rendered template with two replacements', () => {
const templateString = 'string with {{one}} replacement, and a {{two}} one.';
const result = renderTemplate(templateString, { one: '1', two: '2nd' });
expect(result).to.be('string with 1 replacement, and a 2nd one.');
expect(result).toBe('string with 1 replacement, and a 2nd one.');
});
});
});

View file

@ -4,15 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
// A simple template renderer, it replaces mustache/angular style {{...}} tags with
// the values provided via the data object
export function renderTemplate(str, data) {
export function renderTemplate(str: string, data?: Record<string, string>): string {
const matches = str.match(/{{(.*?)}}/g);
if (Array.isArray(matches)) {
if (Array.isArray(matches) && data !== undefined) {
matches.forEach(v => {
str = str.replace(v, data[v.replace(/{{|}}/g, '')]);
});
@ -20,17 +17,3 @@ export function renderTemplate(str, data) {
return str;
}
export function stringHash(str) {
let hash = 0;
let chr = '';
if (str.length === 0) {
return hash;
}
for (let i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return hash < 0 ? hash * -2 : hash;
}

View file

@ -1,7 +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.
*/
export function isValidJson(json: string): boolean;

View file

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { VALIDATION_STATUS } from '../constants/validation';
// get the most severe status level from a list of messages
const contains = (arr, str) => arr.findIndex(v => v === str) >= 0;
export function getMostSevereMessageStatus(messages) {
const statuses = messages.map(m => m.status);
return [
VALIDATION_STATUS.INFO,
VALIDATION_STATUS.WARNING,
VALIDATION_STATUS.ERROR
].reduce((previous, current) => {
return contains(statuses, current) ? current : previous;
}, VALIDATION_STATUS.SUCCESS);
}
// extends an angular directive's scope with the necessary methods
// needed to embed the job validation button
export function addJobValidationMethods($scope, service) {
$scope.getDuration = () => ({
start: $scope.formConfig.start,
end: $scope.formConfig.end
});
// isCurrentJobConfig is used to track if the form configuration
// changed since the last job validation was done
$scope.isCurrentJobConfig = false;
// need to pass true as third argument here to track granular changes
$scope.$watch('formConfig', () => { $scope.isCurrentJobConfig = false; }, true);
$scope.getJobConfig = () => {
$scope.isCurrentJobConfig = true;
return service.getJobFromConfig($scope.formConfig);
};
}
export function isValidJson(json) {
if(json === null) {
return false;
}
try {
JSON.parse(json);
return true;
} catch (error) {
return false;
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { VALIDATION_STATUS } from '../constants/validation';
// get the most severe status level from a list of messages
const contains = (arr: string[], str: string) => arr.indexOf(str) >= 0;
export function getMostSevereMessageStatus(messages: Array<{ status: string }>): VALIDATION_STATUS {
const statuses = messages.map(m => m.status);
return [VALIDATION_STATUS.INFO, VALIDATION_STATUS.WARNING, VALIDATION_STATUS.ERROR].reduce(
(previous, current) => {
return contains(statuses, current) ? current : previous;
},
VALIDATION_STATUS.SUCCESS
);
}
export function isValidJson(json: string) {
if (json === null) {
return false;
}
try {
JSON.parse(json);
return true;
} catch (error) {
return false;
}
}

0
x-pack/legacy/plugins/ml/index.ts Normal file → Executable file
View file

View file

@ -33,7 +33,7 @@ import { ml } from '../../services/ml_api_service';
import { mlJobService } from '../../services/job_service';
import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_utils';
import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils';
import { getIndexPatterns } from '../../util/index_utils';
import { getIndexPatternIdFromName } from '../../util/index_utils';
import { replaceStringTokens } from '../../util/string_utils';
@ -214,7 +214,6 @@ export const LinksMenu = injectI18n(class LinksMenu extends Component {
const { intl } = this.props;
const categoryId = this.props.anomaly.entityValue;
const record = this.props.anomaly.source;
const indexPatterns = getIndexPatterns();
const job = mlJobService.getJob(this.props.anomaly.jobId);
if (job === undefined) {
@ -260,13 +259,7 @@ export const LinksMenu = injectI18n(class LinksMenu extends Component {
// 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.
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) || 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

View file

@ -17,6 +17,5 @@ export const kibanaContextValueMock = {
currentIndexPattern: indexPatternMock,
currentSavedSearch: savedSearchMock,
indexPatterns: indexPatternsMock,
kbnBaseUrl: 'url',
kibanaConfig: kibanaConfigMock,
};

View file

@ -21,7 +21,6 @@ export interface KibanaContextValue {
currentIndexPattern: IndexPattern;
currentSavedSearch: SavedSearch;
indexPatterns: IndexPatterns;
kbnBaseUrl: string;
kibanaConfig: KibanaConfigTypeFix;
}

View file

@ -16,7 +16,6 @@ export const useKibanaContext = () => {
context.currentIndexPattern === undefined ||
context.currentSavedSearch === undefined ||
context.indexPatterns === undefined ||
context.kbnBaseUrl === undefined ||
context.kibanaConfig === undefined
) {
throw new Error('required attribute is undefined');

View file

@ -13,10 +13,9 @@ const module = uiModules.get('apps/ml', ['react']);
import { IndexPatterns } from 'ui/index_patterns';
import { I18nContext } from 'ui/i18n';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../../common/types/angular';
import { SearchItemsProvider } from '../../../jobs/new_job_new/utils/new_job_utils';
import { createSearchItems } from '../../../jobs/new_job_new/utils/new_job_utils';
import { KibanaConfigTypeFix, KibanaContext } from '../../../contexts/kibana';
@ -31,19 +30,20 @@ module.directive('mlDataFrameAnalyticsExploration', ($injector: InjectorService)
globalState.fetch();
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -13,10 +13,8 @@ const module = uiModules.get('apps/ml', ['react']);
import { IndexPatterns } from 'ui/index_patterns';
import { I18nContext } from 'ui/i18n';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../../common/types/angular';
import { SearchItemsProvider } from '../../../jobs/new_job_new/utils/new_job_utils';
import { createSearchItems } from '../../../jobs/new_job_new/utils/new_job_utils';
import { KibanaConfigTypeFix, KibanaContext } from '../../../contexts/kibana';
@ -28,19 +26,20 @@ module.directive('mlDataFrameAnalyticsManagement', ($injector: InjectorService)
restrict: 'E',
link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => {
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -4,38 +4,52 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nContext } from 'ui/i18n';
import { wrapInI18nContext } from 'ui/i18n';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import uiRoutes from 'ui/routes';
import { getDataVisualizerBreadcrumbs } from './breadcrumbs';
import { checkBasicLicense } from '../license/check_license';
import { checkFindFileStructurePrivilege } from '../privilege/check_privilege';
import uiRoutes from 'ui/routes';
const template = `
<div class="euiSpacer euiSpacer--s" />
<datavisualizer-selector data-test-subj="mlPageDataVisualizerSelector" />
`;
uiRoutes
.when('/datavisualizer', {
template,
k7Breadcrumbs: getDataVisualizerBreadcrumbs,
resolve: {
CheckLicense: checkBasicLicense,
privileges: checkFindFileStructurePrivilege,
}
});
uiRoutes.when('/datavisualizer', {
template,
k7Breadcrumbs: getDataVisualizerBreadcrumbs,
resolve: {
CheckLicense: checkBasicLicense,
privileges: checkFindFileStructurePrivilege,
},
});
// @ts-ignore
import { DatavisualizerSelector } from './datavisualizer_selector';
module.directive('datavisualizerSelector', function ($injector) {
const reactDirective = $injector.get('reactDirective');
module.directive('datavisualizerSelector', function() {
return {
scope: {},
restrict: 'E',
link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => {
ReactDOM.render(
<I18nContext>
<DatavisualizerSelector />
</I18nContext>,
element[0]
);
return reactDirective(wrapInI18nContext(DatavisualizerSelector), undefined, { restrict: 'E' }, { });
element.on('$destroy', () => {
ReactDOM.unmountComponentAtNode(element[0]);
scope.$destroy();
});
},
};
});

View file

@ -7,7 +7,7 @@
import { FormattedMessage } from '@kbn/i18n/react';
import React, {
Component,
Component
} from 'react';
import {

View file

@ -25,7 +25,7 @@ import { ImportErrors } from '../import_errors';
import { ImportSummary } from '../import_summary';
import { ImportSettings } from '../import_settings';
import { ExperimentalBadge } from '../experimental_badge';
import { getIndexPatternNames, refreshIndexPatterns } from '../../../../util/index_utils';
import { getIndexPatternNames, loadIndexPatterns } from '../../../../util/index_utils';
import { ml } from '../../../../services/ml_api_service';
import { hasImportPermission } from '../utils';
@ -359,7 +359,7 @@ export class ImportView extends Component {
}
async loadIndexPatternNames() {
await refreshIndexPatterns();
await loadIndexPatterns();
const indexPatternNames = getIndexPatternNames();
this.setState({ indexPatternNames });
}

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import { getFileDataVisualizerBreadcrumbs } from './breadcrumbs';
import { checkBasicLicense } from '../../license/check_license';
import { checkFindFileStructurePrivilege } from '../../privilege/check_privilege';
import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import { loadMlServerInfo } from '../../services/ml_server_info';
import { loadIndexPatterns } from '../../util/index_utils';
import { FileDataVisualizerPage } from './file_datavisualizer';
import uiRoutes from 'ui/routes';
const template = `
<div class="euiSpacer euiSpacer--s" />
<file-datavisualizer-page />
`;
uiRoutes
.when('/filedatavisualizer/?', {
template,
k7Breadcrumbs: getFileDataVisualizerBreadcrumbs,
resolve: {
CheckLicense: checkBasicLicense,
privileges: checkFindFileStructurePrivilege,
indexPatterns: loadIndexPatterns,
mlNodeCount: getMlNodeCount,
loadMlServerInfo,
}
});
module.directive('fileDatavisualizerPage', function ($injector) {
const reactDirective = $injector.get('reactDirective');
const indexPatterns = $injector.get('indexPatterns');
const kibanaConfig = $injector.get('config');
return reactDirective(wrapInI18nContext(FileDataVisualizerPage), undefined, { restrict: 'E' }, { indexPatterns, kibanaConfig });
});

View file

@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nContext } from 'ui/i18n';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import uiRoutes from 'ui/routes';
import { IndexPatterns } from 'ui/index_patterns';
import { KibanaConfigTypeFix } from '../../contexts/kibana';
// @ts-ignore
import { getFileDataVisualizerBreadcrumbs } from './breadcrumbs';
import { InjectorService } from '../../../common/types/angular';
import { checkBasicLicense } from '../../license/check_license';
import { checkFindFileStructurePrivilege } from '../../privilege/check_privilege';
import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import { loadMlServerInfo } from '../../services/ml_server_info';
import { loadIndexPatterns } from '../../util/index_utils';
// @ts-ignore
import { FileDataVisualizerPage } from './file_datavisualizer';
const template = `
<div class="euiSpacer euiSpacer--s" />
<file-datavisualizer-page />
`;
uiRoutes.when('/filedatavisualizer/?', {
template,
k7Breadcrumbs: getFileDataVisualizerBreadcrumbs,
resolve: {
CheckLicense: checkBasicLicense,
privileges: checkFindFileStructurePrivilege,
indexPatterns: loadIndexPatterns,
mlNodeCount: getMlNodeCount,
loadMlServerInfo,
},
});
interface Props {
indexPatterns: IndexPatterns;
kibanaConfig: KibanaConfigTypeFix;
}
module.directive('fileDatavisualizerPage', function($injector: InjectorService) {
return {
scope: {},
restrict: 'E',
link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => {
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const props: Props = {
indexPatterns,
kibanaConfig,
};
ReactDOM.render(
<I18nContext>{React.createElement(FileDataVisualizerPage, props)}</I18nContext>,
element[0]
);
element.on('$destroy', () => {
ReactDOM.unmountComponentAtNode(element[0]);
scope.$destroy();
});
},
};
});

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './file_datavisualizer_directive';

View file

@ -13,11 +13,10 @@ const module = uiModules.get('apps/ml', ['react']);
import { I18nContext } from 'ui/i18n';
import { IndexPatterns } from 'ui/index_patterns';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../common/types/angular';
import { KibanaConfigTypeFix, KibanaContext } from '../../contexts/kibana/kibana_context';
import { SearchItemsProvider } from '../../jobs/new_job_new/utils/new_job_utils';
import { createSearchItems } from '../../jobs/new_job_new/utils/new_job_utils';
import { Page } from './page';
@ -27,19 +26,20 @@ module.directive('mlDataVisualizer', ($injector: InjectorService) => {
restrict: 'E',
link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => {
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -23,7 +23,7 @@ import {
import { getAnomalyExplorerBreadcrumbs } from './breadcrumbs';
import { checkFullLicense } from '../license/check_license';
import { checkGetJobsPrivilege } from '../privilege/check_privilege';
import { getIndexPatterns, loadIndexPatterns } from '../util/index_utils';
import { loadIndexPatterns } from '../util/index_utils';
import { TimeBuckets } from 'plugins/ml/util/time_buckets';
import { explorer$ } from './explorer_dashboard_service';
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
@ -114,7 +114,7 @@ module.controller('MlExplorerController', function (
}
// Populate the map of jobs / detectors / field formatters for the selected IDs.
mlFieldFormatService.populateFormats(selectedJobIds, getIndexPatterns())
mlFieldFormatService.populateFormats(selectedJobIds)
.catch((err) => {
console.log('Error populating field formats:', err);
})

View file

@ -30,7 +30,6 @@ export const DedicatedIndexSwitch: FC = () => {
checked={useDedicatedIndex}
onChange={toggleModelPlot}
data-test-subj="mlJobWizardSwitchUseDedicatedIndex"
showLabel={false}
label={i18n.translate(
'xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.useDedicatedIndex.title',
{

View file

@ -30,7 +30,6 @@ export const ModelPlotSwitch: FC = () => {
checked={modelPlotEnabled}
onChange={toggleModelPlot}
data-test-subj="mlJobWizardSwitchModelPlot"
showLabel={false}
label={i18n.translate(
'xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.enableModelPlot.title',
{

View file

@ -44,7 +44,6 @@ export const SparseDataSwitch: FC = () => {
checked={sparseData}
onChange={toggleSparseData}
data-test-subj="mlJobWizardSwitchSparseData"
showLabel={false}
label={i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.sparseData.title', {
defaultMessage: 'Sparse data',
})}

View file

@ -14,10 +14,8 @@ import { timefilter } from 'ui/timefilter';
import { IndexPatterns } from 'ui/index_patterns';
import { I18nContext } from 'ui/i18n';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../../../common/types/angular';
import { SearchItemsProvider } from '../../../new_job_new/utils/new_job_utils';
import { createSearchItems } from '../../../new_job_new/utils/new_job_utils';
import { Page } from './page';
import { KibanaContext, KibanaConfigTypeFix } from '../../../../contexts/kibana';
@ -32,18 +30,19 @@ module.directive('mlJobTypePage', ($injector: InjectorService) => {
timefilter.disableAutoRefreshSelector();
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -14,10 +14,8 @@ import { timefilter } from 'ui/timefilter';
import { IndexPatterns } from 'ui/index_patterns';
import { I18nContext } from 'ui/i18n';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../../../common/types/angular';
import { SearchItemsProvider } from '../../utils/new_job_utils';
import { createSearchItems } from '../../utils/new_job_utils';
import { Page, PageProps } from './page';
import { JOB_TYPE } from '../../common/job_creator/util/constants';
@ -32,9 +30,7 @@ module.directive('mlNewJobPage', ($injector: InjectorService) => {
timefilter.disableAutoRefreshSelector();
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const existingJobsAndGroups = $route.current.locals.existingJobsAndGroups;
@ -43,15 +39,17 @@ module.directive('mlNewJobPage', ($injector: InjectorService) => {
}
const jobType: JOB_TYPE = $route.current.locals.jobType;
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -247,7 +247,6 @@ export const JobSettingsForm: FC<JobSettingsFormProps> = ({
useDedicatedIndex: checked,
});
}}
showLabel={false}
label={i18n.translate('xpack.ml.newJob.recognize.useDedicatedIndexLabel', {
defaultMessage: 'Use dedicated index',
})}

View file

@ -14,10 +14,9 @@ import { timefilter } from 'ui/timefilter';
import { IndexPatterns } from 'ui/index_patterns';
import { I18nContext } from 'ui/i18n';
import { IPrivate } from 'ui/private';
import { InjectorService } from '../../../../common/types/angular';
import { SearchItemsProvider } from '../../new_job_new/utils/new_job_utils';
import { createSearchItems } from '../../new_job_new/utils/new_job_utils';
import { Page } from './page';
import { KibanaContext, KibanaConfigTypeFix } from '../../../contexts/kibana';
@ -32,22 +31,22 @@ module.directive('mlRecognizePage', ($injector: InjectorService) => {
timefilter.disableAutoRefreshSelector();
const indexPatterns = $injector.get<IndexPatterns>('indexPatterns');
const kbnBaseUrl = $injector.get<string>('kbnBaseUrl');
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const Private = $injector.get<IPrivate>('Private');
const $route = $injector.get<any>('$route');
const moduleId = $route.current.params.id;
const existingGroupIds: string[] = $route.current.locals.existingJobsAndGroups.groupIds;
const createSearchItems = Private(SearchItemsProvider);
const { indexPattern, savedSearch, combinedQuery } = createSearchItems();
const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
kibanaConfig,
$route.current.locals.indexPattern,
$route.current.locals.savedSearch
);
const kibanaContext = {
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
indexPatterns,
kbnBaseUrl,
kibanaConfig,
};

View file

@ -7,7 +7,6 @@
import chrome from 'ui/chrome';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { IPrivate } from 'ui/private';
import { mlJobService } from '../../../services/job_service';
import { ml } from '../../../services/ml_api_service';
import { KibanaObjects } from './page';
@ -17,12 +16,7 @@ import { KibanaObjects } from './page';
* Redirects to the Anomaly Explorer to view the jobs if they have been created,
* or the recognizer job wizard for the module if not.
*/
export function checkViewOrCreateJobs(
Private: IPrivate,
$route: any,
kbnBaseUrl: string,
kbnUrl: any
) {
export function checkViewOrCreateJobs($route: any) {
return new Promise((resolve, reject) => {
const moduleId = $route.current.params.id;
const indexPatternId = $route.current.params.index;
@ -58,7 +52,7 @@ export function checkViewOrCreateJobs(
}),
});
kbnUrl.redirect(`/jobs`);
window.location.href = '#/jobs';
reject();
});
});

View file

@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { IndexPattern } from 'ui/index_patterns';
import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types';
import { KibanaConfigTypeFix } from '../../../contexts/kibana';
import { InjectorService } from '../../../../common/types/angular';
import { esQuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public';
export interface SearchItems {
@ -18,55 +18,50 @@ export interface SearchItems {
// Provider for creating the items used for searching and job creation.
// Uses the $route object to retrieve the indexPattern and savedSearch from the url
export function SearchItemsProvider($injector: InjectorService) {
const kibanaConfig = $injector.get<KibanaConfigTypeFix>('config');
const $route = $injector.get<any>('$route');
function createSearchItems() {
let indexPattern = $route.current.locals.indexPattern;
export function createSearchItems(
kibanaConfig: KibanaConfigTypeFix,
indexPattern: IndexPattern,
savedSearch: SavedSearch
) {
// query is only used by the data visualizer as it needs
// a lucene query_string.
// Using a blank query will cause match_all:{} to be used
// when passed through luceneStringToDsl
let query = {
query: '',
language: 'lucene',
};
// query is only used by the data visualizer as it needs
// a lucene query_string.
// Using a blank query will cause match_all:{} to be used
// when passed through luceneStringToDsl
let query = {
query: '',
language: 'lucene',
};
let combinedQuery: any = {
bool: {
must: [
{
match_all: {},
},
],
},
};
let combinedQuery: any = {
bool: {
must: [
{
match_all: {},
},
],
},
};
if (indexPattern.id === undefined && savedSearch.id !== undefined) {
const searchSource = savedSearch.searchSource;
indexPattern = searchSource.getField('index');
const savedSearch = $route.current.locals.savedSearch;
if (indexPattern.id === undefined && savedSearch.id !== undefined) {
const searchSource = savedSearch.searchSource;
indexPattern = searchSource.getField('index');
query = searchSource.getField('query');
const fs = searchSource.getField('filter');
query = searchSource.getField('query');
const fs = searchSource.getField('filter');
const filters = fs.length ? fs : [];
const filters = fs.length ? fs : [];
const esQueryConfigs = esQuery.getEsQueryConfig(kibanaConfig);
combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs);
}
return {
indexPattern,
savedSearch,
query,
combinedQuery,
};
const esQueryConfigs = esQuery.getEsQueryConfig(kibanaConfig);
combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs);
}
return createSearchItems;
return {
indexPattern,
savedSearch,
query,
combinedQuery,
};
}
// Only model plot cardinality relevant

View file

@ -4,51 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
// @ts-ignore No declaration file for module
import { banners } from 'ui/notify';
import { EuiCallOut } from '@elastic/eui';
// @ts-ignore No declaration file for module
import { xpackInfo } from '../../../xpack_main/public/services/xpack_info';
import { banners, addAppRedirectMessageToUrl } from 'ui/notify';
import { LICENSE_TYPE } from '../../common/constants/license';
import { LICENSE_STATUS_VALID } from '../../../../common/constants/license_status';
import chrome from 'ui/chrome';
import { EuiCallOut } from '@elastic/eui';
let licenseHasExpired = true;
let licenseType = null;
let expiredLicenseBannerId;
let licenseType: LICENSE_TYPE | null = null;
let expiredLicenseBannerId: string;
export function checkFullLicense(kbnBaseUrl, kbnUrl) {
export function checkFullLicense() {
const features = getFeatures();
licenseType = features.licenseType;
if (features.isAvailable === false) {
// ML is not enabled
return redirectToKibana(features, kbnBaseUrl);
return redirectToKibana();
} else if (features.licenseType === LICENSE_TYPE.BASIC) {
// ML is enabled, but only with a basic or gold license
return redirectToBasic(kbnUrl);
return redirectToBasic();
} else {
// ML is enabled
setLicenseExpired(features);
return Promise.resolve(features);
}
}
export function checkBasicLicense(kbnBaseUrl) {
export function checkBasicLicense() {
const features = getFeatures();
licenseType = features.licenseType;
if (features.isAvailable === false) {
// ML is not enabled
return redirectToKibana(features, kbnBaseUrl);
return redirectToKibana();
} else {
// ML is enabled
setLicenseExpired(features);
return Promise.resolve(features);
@ -58,38 +51,32 @@ export function checkBasicLicense(kbnBaseUrl) {
// a wrapper for checkFullLicense which doesn't resolve if the license has expired.
// this is used by all create jobs pages to redirect back to the jobs list
// if the user's license has expired.
export function checkLicenseExpired(kbnBaseUrl, kbnUrl) {
return checkFullLicense(kbnBaseUrl, kbnUrl)
.then((features) => {
export function checkLicenseExpired() {
return checkFullLicense()
.then((features: any) => {
if (features.hasExpired) {
kbnUrl.redirect('/jobs');
return Promise.halt();
window.location.href = '#/jobs';
return Promise.reject();
} else {
return Promise.resolve(features);
}
})
.catch(() => {
return Promise.halt();
return Promise.reject();
});
}
function setLicenseExpired(features) {
licenseHasExpired = (features.hasExpired || false);
function setLicenseExpired(features: any) {
licenseHasExpired = features.hasExpired || false;
// If the license has expired ML app will still work for 7 days and then
// the job management endpoints (e.g. create job, start datafeed) will be restricted.
// Therefore we need to keep the app enabled but show an info banner to the user.
if(licenseHasExpired) {
if (licenseHasExpired) {
const message = features.message;
if (expiredLicenseBannerId === undefined) {
// Only show the banner once with no way to dismiss it
expiredLicenseBannerId = banners.add({
component: (
<EuiCallOut
iconType="iInCircle"
color="warning"
title={message}
/>
),
component: <EuiCallOut iconType="iInCircle" color="warning" title={message} />,
});
}
}
@ -99,16 +86,14 @@ function getFeatures() {
return xpackInfo.get('features.ml');
}
function redirectToKibana(features, kbnBaseUrl) {
const { message } = features;
const newUrl = addAppRedirectMessageToUrl(chrome.addBasePath(kbnBaseUrl), (message || ''));
window.location.href = newUrl;
return Promise.halt();
function redirectToKibana() {
window.location.href = '/';
return Promise.reject();
}
function redirectToBasic(kbnUrl) {
kbnUrl.redirect('/datavisualizer');
return Promise.halt();
function redirectToBasic() {
window.location.href = '#/datavisualizer';
return Promise.reject();
}
export function hasLicenseExpired() {
@ -116,10 +101,10 @@ export function hasLicenseExpired() {
}
export function isFullLicense() {
return (licenseType === LICENSE_TYPE.FULL);
return licenseType === LICENSE_TYPE.FULL;
}
export function xpackFeatureAvailable(feature) {
export function xpackFeatureAvailable(feature: string) {
// each plugin can register their own set of features.
// so we need specific checks for each one.
// this list can grow if we need to check other plugin's features.

View file

@ -15,18 +15,18 @@ import { ACCESS_DENIED_PATH } from '../management/management_urls';
let privileges: Privileges = getDefaultPrivileges();
// manage_ml requires all monitor and admin cluster privileges: https://github.com/elastic/elasticsearch/blob/664a29c8905d8ce9ba8c18aa1ed5c5de93a0eabc/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java#L53
export function canGetManagementMlJobs(kbnUrl: any) {
export function canGetManagementMlJobs() {
return new Promise((resolve, reject) => {
getManageMlPrivileges().then(
({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => {
privileges = capabilities;
// Loop through all privilages to ensure they are all set to true.
// Loop through all privileges to ensure they are all set to true.
const isManageML = Object.values(privileges).every(p => p === true);
if (isManageML === true && isPlatinumOrTrialLicense === true) {
return resolve({ mlFeatureEnabledInSpace });
} else {
kbnUrl.redirect(ACCESS_DENIED_PATH);
window.location.href = ACCESS_DENIED_PATH;
return reject();
}
}
@ -34,7 +34,7 @@ export function canGetManagementMlJobs(kbnUrl: any) {
});
}
export function checkGetJobsPrivilege(kbnUrl: any): Promise<Privileges> {
export function checkGetJobsPrivilege(): Promise<Privileges> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities, isPlatinumOrTrialLicense }) => {
privileges = capabilities;
@ -46,14 +46,14 @@ export function checkGetJobsPrivilege(kbnUrl: any): Promise<Privileges> {
if (privileges.canGetJobs || isPlatinumOrTrialLicense === false) {
return resolve(privileges);
} else {
kbnUrl.redirect('/access-denied');
window.location.href = '#/access-denied';
return reject();
}
});
});
}
export function checkCreateJobsPrivilege(kbnUrl: any): Promise<Privileges> {
export function checkCreateJobsPrivilege(): Promise<Privileges> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities, isPlatinumOrTrialLicense }) => {
privileges = capabilities;
@ -65,14 +65,14 @@ export function checkCreateJobsPrivilege(kbnUrl: any): Promise<Privileges> {
} else {
// if the user has no permission to create a job,
// redirect them back to the Transforms Management page
kbnUrl.redirect('/jobs');
window.location.href = '#/jobs';
return reject();
}
});
});
}
export function checkFindFileStructurePrivilege(kbnUrl: any): Promise<Privileges> {
export function checkFindFileStructurePrivilege(): Promise<Privileges> {
return new Promise((resolve, reject) => {
getPrivileges().then(({ capabilities }) => {
privileges = capabilities;
@ -81,7 +81,7 @@ export function checkFindFileStructurePrivilege(kbnUrl: any): Promise<Privileges
if (privileges.canFindFileStructure) {
return resolve(privileges);
} else {
kbnUrl.redirect('/access-denied');
window.location.href = '#/access-denied';
return reject();
}
});

View file

@ -4,21 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import { IndexPattern } from 'ui/index_patterns';
import { mlFunctionToESAggregation } from '../../common/util/job_utils';
import { getIndexPatternById } from '../util/index_utils';
import { getIndexPatternById, getIndexPatternIdFromName } from '../util/index_utils';
import { mlJobService } from '../services/job_service';
type FormatsByJobId = Record<string, any>;
type IndexPatternIdsByJob = Record<string, any>;
// Service for accessing FieldFormat objects configured for a Kibana index pattern
// for use in formatting the actual and typical values from anomalies.
class FieldFormatService {
constructor() {
this.indexPatternIdsByJob = {};
this.formatsByJob = {};
}
indexPatternIdsByJob: IndexPatternIdsByJob = {};
formatsByJob: FormatsByJobId = {};
// 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
@ -26,60 +24,57 @@ class FieldFormatService {
// configured in the datafeed of each job.
// Builds a map of Kibana FieldFormats (plugins/data/common/field_formats)
// against detector index by job ID.
populateFormats(jobIds, indexPatterns) {
populateFormats(jobIds: string[]) {
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) => {
jobIds.forEach(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 id = getIndexPatternIdFromName(datafeedIndices.length ? datafeedIndices[0] : '');
if (id !== null) {
this.indexPatternIdsByJob[jobId] = id;
}
});
const promises = jobIds.map(jobId => Promise.all([
this.getFormatsForJob(jobId)
]));
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];
Promise.all(promises)
.then(fmtsByJobByDetector => {
fmtsByJobByDetector.forEach((formatsByDetector, i) => {
this.formatsByJob[jobIds[i]] = formatsByDetector[0];
});
resolve(this.formatsByJob);
})
.catch(err => {
reject({ formats: {}, err });
});
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]);
getFieldFormat(jobId: string, detectorIndex: number) {
if (this.formatsByJob.hasOwnProperty(jobId)) {
return 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) {
getFieldFormatFromIndexPattern(
fullIndexPattern: IndexPattern,
fieldName: string,
esAggName: string
) {
// 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;
let fieldFormat;
if (esAggName !== 'cardinality') {
const fieldList = _.get(fullIndexPattern, 'fields', []);
const fieldList = fullIndexPattern.fields;
const field = fieldList.getByName(fieldName);
if (field !== undefined) {
fieldFormat = field.format;
@ -89,34 +84,34 @@ class FieldFormatService {
return fieldFormat;
}
getFormatsForJob(jobId) {
getFormatsForJob(jobId: string): Promise<any[]> {
return new Promise((resolve, reject) => {
const jobObj = mlJobService.getJob(jobId);
const detectors = jobObj.analysis_config.detectors || [];
const formatsByDetector = {};
const formatsByDetector: any[] = [];
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) => {
.then(indexPatternData => {
// Store the FieldFormat for each job by detector_index.
const fieldList = _.get(indexPatternData, 'fields', []);
_.each(detectors, (dtr) => {
const fieldList = indexPatternData.fields;
detectors.forEach(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') {
const field = fieldList.getByName(dtr.field_name);
if (field !== undefined) {
formatsByDetector[dtr.detector_index] = field.format;
formatsByDetector[dtr.detector_index!] = field.format;
}
}
});
resolve(formatsByDetector);
}).catch(err => {
})
.catch(err => {
reject(err);
});
} else {

View file

@ -34,6 +34,7 @@ declare interface JobService {
createResultsUrl(jobId: string[], start: number, end: number, location: string): string;
getJobAndGroupIds(): ExistingJobsAndGroups;
searchPreview(job: CombinedJob): Promise<SearchResponse<any>>;
getJob(jobId: string): CombinedJob;
}
export const mlJobService: JobService;

View file

@ -4,19 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ML_BREADCRUMB, ANOMALY_DETECTION_BREADCRUMB, SETTINGS } from '../breadcrumbs';
import { i18n } from '@kbn/i18n';
import { ML_BREADCRUMB, ANOMALY_DETECTION_BREADCRUMB, SETTINGS } from '../breadcrumbs';
export function getSettingsBreadcrumbs() {
// Whilst top level nav menu with tabs remains,
// use root ML breadcrumb.
return [
ML_BREADCRUMB,
ANOMALY_DETECTION_BREADCRUMB,
SETTINGS
];
return [ML_BREADCRUMB, ANOMALY_DETECTION_BREADCRUMB, SETTINGS];
}
export function getCalendarManagementBreadcrumbs() {
@ -24,10 +18,10 @@ export function getCalendarManagementBreadcrumbs() {
...getSettingsBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagementLabel', {
defaultMessage: 'Calendar management'
defaultMessage: 'Calendar management',
}),
href: '#/settings/calendars_list'
}
href: '#/settings/calendars_list',
},
];
}
@ -36,10 +30,10 @@ export function getCreateCalendarBreadcrumbs() {
...getCalendarManagementBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagement.createLabel', {
defaultMessage: 'Create'
defaultMessage: 'Create',
}),
href: '#/settings/calendars_list/new_calendar'
}
href: '#/settings/calendars_list/new_calendar',
},
];
}
@ -48,10 +42,10 @@ export function getEditCalendarBreadcrumbs() {
...getCalendarManagementBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.calendarManagement.editLabel', {
defaultMessage: 'Edit'
defaultMessage: 'Edit',
}),
href: '#/settings/calendars_list/edit_calendar'
}
href: '#/settings/calendars_list/edit_calendar',
},
];
}
@ -60,10 +54,10 @@ export function getFilterListsBreadcrumbs() {
...getSettingsBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterListsLabel', {
defaultMessage: 'Filter lists'
defaultMessage: 'Filter lists',
}),
href: '#/settings/filter_lists'
}
href: '#/settings/filter_lists',
},
];
}
@ -72,10 +66,10 @@ export function getCreateFilterListBreadcrumbs() {
...getFilterListsBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterLists.createLabel', {
defaultMessage: 'Create'
defaultMessage: 'Create',
}),
href: '#/settings/filter_lists/new'
}
href: '#/settings/filter_lists/new',
},
];
}
@ -84,9 +78,9 @@ export function getEditFilterListBreadcrumbs() {
...getFilterListsBreadcrumbs(),
{
text: i18n.translate('xpack.ml.settings.breadcrumbs.filterLists.editLabel', {
defaultMessage: 'Edit'
defaultMessage: 'Edit',
}),
href: '#/settings/filter_lists/edit'
}
href: '#/settings/filter_lists/edit',
},
];
}

View file

@ -4,21 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { checkFullLicense } from '../../../license/check_license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import { checkMlNodesAvailable } from '../../../ml_nodes_check';
import { getCreateCalendarBreadcrumbs, getEditCalendarBreadcrumbs } from '../../breadcrumbs';
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { NewCalendar } from './new_calendar';
const template = `
<div class="euiSpacer euiSpacer--s" />
@ -33,7 +34,7 @@ uiRoutes
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
checkMlNodesAvailable,
}
},
})
.when('/settings/calendars_list/edit_calendar/:calendarId', {
template,
@ -42,21 +43,19 @@ uiRoutes
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
checkMlNodesAvailable,
}
},
});
import { NewCalendar } from './new_calendar.js';
module.directive('mlNewCalendar', function ($route) {
module.directive('mlNewCalendar', function($route: any) {
return {
restrict: 'E',
replace: false,
scope: {},
link: function (scope, element) {
link(scope: ng.IScope, element: ng.IAugmentedJQuery) {
const props = {
calendarId: $route.current.params.calendarId,
canCreateCalendar: checkPermission('canCreateCalendar'),
canDeleteCalendar: checkPermission('canDeleteCalendar')
canDeleteCalendar: checkPermission('canDeleteCalendar'),
};
ReactDOM.render(
@ -65,6 +64,6 @@ module.directive('mlNewCalendar', function ($route) {
</I18nContext>,
element[0]
);
}
},
};
});

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './directive';

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FC } from 'react';
declare const NewCalendar: FC<{
calendarId: string;
canCreateCalendar: boolean;
canDeleteCalendar: boolean;
}>;

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FC } from 'react';
declare const CalendarsList: FC<{
canCreateCalendar: boolean;
canDeleteCalendar: boolean;
}>;

View file

@ -8,16 +8,18 @@ import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { checkFullLicense } from '../../../license/check_license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { getCalendarManagementBreadcrumbs } from '../../breadcrumbs';
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { CalendarsList } from './calendars_list';
const template = `
<div class="euiSpacer euiSpacer--s" />
@ -34,14 +36,12 @@ uiRoutes.when('/settings/calendars_list', {
},
});
import { CalendarsList } from './calendars_list';
module.directive('mlCalendarsList', function () {
module.directive('mlCalendarsList', function() {
return {
restrict: 'E',
replace: false,
scope: {},
link: function (scope, element) {
link(scope: ng.IScope, element: ng.IAugmentedJQuery) {
const props = {
canCreateCalendar: checkPermission('canCreateCalendar'),
canDeleteCalendar: checkPermission('canDeleteCalendar'),

View file

@ -4,6 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './directive';

View file

@ -4,22 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import { getCreateFilterListBreadcrumbs, getEditFilterListBreadcrumbs } from '../../breadcrumbs';
import { checkFullLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege';
import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { EditFilterList } from './edit_filter_list';
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import { checkFullLicense } from '../../../license/check_license';
import { getCreateFilterListBreadcrumbs, getEditFilterListBreadcrumbs } from '../../breadcrumbs';
import { EditFilterList } from './edit_filter_list';
const template = `
<div class="euiSpacer euiSpacer--s" />
@ -34,7 +34,7 @@ uiRoutes
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
}
},
})
.when('/settings/filter_lists/edit_filter_list/:filterId', {
template,
@ -43,15 +43,15 @@ uiRoutes
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
}
},
});
module.directive('mlEditFilterList', function ($route) {
module.directive('mlEditFilterList', function($route: any) {
return {
restrict: 'E',
replace: false,
scope: {},
link: function (scope, element) {
link(scope: ng.IScope, element: ng.IAugmentedJQuery) {
const props = {
filterId: $route.current.params.filterId,
canCreateFilter: checkPermission('canCreateFilter'),
@ -64,6 +64,6 @@ module.directive('mlEditFilterList', function ($route) {
</I18nContext>,
element[0]
);
}
},
};
});

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FC } from 'react';
declare const EditFilterList: FC<{
filterId: string;
canCreateFilter: boolean;
canDeleteFilter: boolean;
}>;

View file

@ -4,6 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './directive';

View file

@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './edit';
import './list';

View file

@ -4,45 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import { getFilterListsBreadcrumbs } from '../../breadcrumbs';
import { checkFullLicense } from 'plugins/ml/license/check_license';
import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/check_privilege';
import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
import { FilterLists } from './filter_lists';
import uiRoutes from 'ui/routes';
import { I18nContext } from 'ui/i18n';
import { checkFullLicense } from '../../../license/check_license';
import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check_privilege';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { getFilterListsBreadcrumbs } from '../../breadcrumbs';
import { FilterLists } from './filter_lists';
const template = `
<div class="euiSpacer euiSpacer--s" />
<ml-filter-lists />
`;
uiRoutes
.when('/settings/filter_lists', {
template,
k7Breadcrumbs: getFilterListsBreadcrumbs,
resolve: {
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
}
});
uiRoutes.when('/settings/filter_lists', {
template,
k7Breadcrumbs: getFilterListsBreadcrumbs,
resolve: {
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
},
});
module.directive('mlFilterLists', function () {
module.directive('mlFilterLists', function() {
return {
restrict: 'E',
replace: false,
scope: {},
link: function (scope, element) {
link(scope: ng.IScope, element: ng.IAugmentedJQuery) {
const props = {
canCreateFilter: checkPermission('canCreateFilter'),
canDeleteFilter: checkPermission('canDeleteFilter'),
@ -54,6 +53,6 @@ module.directive('mlFilterLists', function () {
</I18nContext>,
element[0]
);
}
},
};
});

View file

@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Duration } from 'moment';
import { FC } from 'react';
export function parseInterval(interval: string): Duration;
declare const FilterLists: FC<{
canCreateFilter: boolean;
canDeleteFilter: boolean;
}>;

View file

@ -4,6 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './directive';

View file

@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './settings_directive';
import './calendars';
import './filter_lists';

View file

@ -4,44 +4,41 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'ngreact';
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/ml', ['react']);
import { checkFullLicense } from '../license/check_license';
import { checkGetJobsPrivilege, checkPermission } from '../privilege/check_privilege';
import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes';
import { getSettingsBreadcrumbs } from './breadcrumbs';
import { I18nContext } from 'ui/i18n';
import uiRoutes from 'ui/routes';
import { timefilter } from 'ui/timefilter';
import { checkFullLicense } from '../license/check_license';
import { checkGetJobsPrivilege, checkPermission } from '../privilege/check_privilege';
import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes';
import { getSettingsBreadcrumbs } from './breadcrumbs';
const template = `
<div class="euiSpacer euiSpacer--s" />
<ml-settings />
`;
uiRoutes
.when('/settings', {
template,
k7Breadcrumbs: getSettingsBreadcrumbs,
resolve: {
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
}
});
uiRoutes.when('/settings', {
template,
k7Breadcrumbs: getSettingsBreadcrumbs,
resolve: {
CheckLicense: checkFullLicense,
privileges: checkGetJobsPrivilege,
mlNodeCount: getMlNodeCount,
},
});
// @ts-ignore
import { Settings } from './settings.js';
module.directive('mlSettings', function () {
module.directive('mlSettings', function() {
const canGetFilters = checkPermission('canGetFilters');
const canGetCalendars = checkPermission('canGetCalendars');
@ -49,7 +46,7 @@ module.directive('mlSettings', function () {
restrict: 'E',
replace: false,
scope: {},
link: function (scope, element) {
link(scope: ng.IScope, element: ng.IAugmentedJQuery) {
timefilter.disableTimeRangeSelector();
timefilter.disableAutoRefreshSelector();
@ -59,6 +56,6 @@ module.directive('mlSettings', function () {
</I18nContext>,
element[0]
);
}
},
};
});

View file

@ -68,7 +68,6 @@ import { mlJobService } from '../services/job_service';
import { mlResultsService } from '../services/results_service';
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
import { getIndexPatterns } from '../util/index_utils';
import { getBoundsRoundedToInterval } from '../util/time_buckets';
import { APP_STATE_ACTION, CHARTS_POINT_TARGET, TIME_FIELD_NAME } from './timeseriesexplorer_constants';
@ -827,7 +826,7 @@ export class TimeSeriesExplorer extends React.Component {
() => {
this.updateControlsForDetector(() => {
// Populate the map of jobs / detectors / field formatters for the selected IDs and refresh.
mlFieldFormatService.populateFormats([jobId], getIndexPatterns())
mlFieldFormatService.populateFormats([jobId])
.catch((err) => { console.log('Error populating field formats:', err); })
// Load the data - if the FieldFormats failed to populate
// the default formatting will be used for metric values.

View file

@ -17,8 +17,6 @@ type IndexPatternSavedObject = SimpleSavedObject<SavedObjectAttributes>;
let indexPatternCache: IndexPatternSavedObject[] = [];
let fullIndexPatterns: IndexPatterns | null = null;
export let refreshIndexPatterns: (() => Promise<IndexPatternSavedObject[]>) | null = null;
export function loadIndexPatterns() {
fullIndexPatterns = data.indexPatterns.indexPatterns;
const savedObjectsClient = chrome.getSavedObjectsClient();
@ -30,20 +28,6 @@ export function loadIndexPatterns() {
})
.then(response => {
indexPatternCache = response.savedObjects;
if (refreshIndexPatterns === null) {
refreshIndexPatterns = () => {
return new Promise((resolve, reject) => {
loadIndexPatterns()
.then(resp => {
resolve(resp);
})
.catch(error => {
reject(error);
});
});
};
}
return indexPatternCache;
});
}
@ -62,7 +46,7 @@ export function getIndexPatternIdFromName(name: string) {
return indexPatternCache[j].id;
}
}
return name;
return null;
}
export function loadCurrentIndexPattern(indexPatterns: IndexPatterns, $route: Record<string, any>) {

View file

@ -4,20 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import sinon from 'sinon';
import { set } from 'lodash';
import { XPackInfo } from '../../../../../../../legacy/plugins/xpack_main/server/lib/xpack_info';
import { checkLicense } from '../check_license';
describe('check_license', () => {
let mockLicenseInfo;
beforeEach(() => mockLicenseInfo = {});
let mockLicenseInfo: XPackInfo;
beforeEach(() => (mockLicenseInfo = {} as XPackInfo));
describe('license information is undefined', () => {
beforeEach(() => mockLicenseInfo = undefined);
beforeEach(() => (mockLicenseInfo = {} as XPackInfo));
it('should set isAvailable to false', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
@ -37,7 +35,7 @@ describe('check_license', () => {
});
describe('license information is not available', () => {
beforeEach(() => mockLicenseInfo.isAvailable = () => false);
beforeEach(() => (mockLicenseInfo.isAvailable = () => false));
it('should set isAvailable to false', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
@ -64,14 +62,21 @@ describe('check_license', () => {
describe('& ML is disabled in Elasticsearch', () => {
beforeEach(() => {
set(mockLicenseInfo, 'feature', sinon.stub().withArgs('ml').returns({ isEnabled: () => false }));
set(
mockLicenseInfo,
'feature',
sinon
.stub()
.withArgs('ml')
.returns({ isEnabled: () => false })
);
});
it ('should set showLinks to false', () => {
it('should set showLinks to false', () => {
expect(checkLicense(mockLicenseInfo).showLinks).to.be(false);
});
it ('should set isAvailable to false', () => {
it('should set isAvailable to false', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
});
@ -86,7 +91,14 @@ describe('check_license', () => {
describe('& ML is enabled in Elasticsearch', () => {
beforeEach(() => {
set(mockLicenseInfo, 'feature', sinon.stub().withArgs('ml').returns({ isEnabled: () => true }));
set(
mockLicenseInfo,
'feature',
sinon
.stub()
.withArgs('ml')
.returns({ isEnabled: () => true })
);
});
describe('& license is trial or platinum', () => {
@ -99,11 +111,11 @@ describe('check_license', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
});
it ('should set showLinks to true', () => {
it('should set showLinks to true', () => {
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
});
it ('should set enableLinks to true', () => {
it('should set enableLinks to true', () => {
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true);
});
@ -119,11 +131,11 @@ describe('check_license', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
});
it ('should set showLinks to true', () => {
it('should set showLinks to true', () => {
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
});
it ('should set enableLinks to true', () => {
it('should set enableLinks to true', () => {
expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true);
});
@ -143,7 +155,7 @@ describe('check_license', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
});
it ('should set showLinks to true', () => {
it('should set showLinks to true', () => {
expect(checkLicense(mockLicenseInfo).showLinks).to.be(true);
});
});

View file

@ -1,17 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { LICENSE_TYPE } from '../../../common/constants/license';
interface Response {
isAvailable: boolean;
showLinks: boolean;
enableLinks: boolean;
licenseType: LICENSE_TYPE;
hasExpired: boolean;
message: string;
}
export function checkLicense(xpackLicenseInfo: any): Response;

View file

@ -4,11 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { LICENSE_TYPE } from '../../../common/constants/license';
import { i18n } from '@kbn/i18n';
import { LICENSE_TYPE } from '../../../common/constants/license';
import { XPackInfo } from '../../../../../../legacy/plugins/xpack_main/server/lib/xpack_info';
export function checkLicense(xpackLicenseInfo) {
interface Response {
isAvailable: boolean;
showLinks: boolean;
enableLinks: boolean;
licenseType?: LICENSE_TYPE;
hasExpired?: boolean;
message?: string;
}
export function checkLicense(xpackLicenseInfo: XPackInfo): Response {
// If, for some reason, we cannot get the license information
// from Elasticsearch, assume worst case and disable the Machine Learning UI
if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
@ -16,9 +25,13 @@ export function checkLicense(xpackLicenseInfo) {
isAvailable: false,
showLinks: true,
enableLinks: false,
message: i18n.translate('xpack.ml.checkLicense.licenseInformationNotAvailableThisTimeMessage', {
defaultMessage: 'You cannot use Machine Learning because license information is not available at this time.'
})
message: i18n.translate(
'xpack.ml.checkLicense.licenseInformationNotAvailableThisTimeMessage',
{
defaultMessage:
'You cannot use Machine Learning because license information is not available at this time.',
}
),
};
}
@ -29,18 +42,15 @@ export function checkLicense(xpackLicenseInfo) {
showLinks: false,
enableLinks: false,
message: i18n.translate('xpack.ml.checkLicense.mlIsUnavailableMessage', {
defaultMessage: 'Machine Learning is unavailable'
})
defaultMessage: 'Machine Learning is unavailable',
}),
};
}
const VALID_FULL_LICENSE_MODES = [
'trial',
'platinum'
];
const VALID_FULL_LICENSE_MODES = ['trial', 'platinum'];
const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_FULL_LICENSE_MODES);
const licenseType = (isLicenseModeValid === true) ? LICENSE_TYPE.FULL : LICENSE_TYPE.BASIC;
const licenseType = isLicenseModeValid === true ? LICENSE_TYPE.FULL : LICENSE_TYPE.BASIC;
const isLicenseActive = xpackLicenseInfo.license.isActive();
const licenseTypeName = xpackLicenseInfo.license.getType();
@ -54,8 +64,8 @@ export function checkLicense(xpackLicenseInfo) {
licenseType,
message: i18n.translate('xpack.ml.checkLicense.licenseHasExpiredMessage', {
defaultMessage: 'Your {licenseTypeName} Machine Learning license has expired.',
values: { licenseTypeName }
})
values: { licenseTypeName },
}),
};
}

View file

@ -9,7 +9,7 @@
import { estimateBucketSpanFactory } from '../../models/bucket_span_estimator';
import { mlFunctionToESAggregation } from '../../../common/util/job_utils';
import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../common/constants/validation';
import { parseInterval } from '../../../common/util/parse_interval.js';
import { parseInterval } from '../../../common/util/parse_interval';
import { validateJobObject } from './validate_job_object';

View file

@ -9,7 +9,7 @@
import _ from 'lodash';
import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server';
import { parseInterval } from '../../../common/util/parse_interval.js';
import { parseInterval } from '../../../common/util/parse_interval';
import { validateJobObject } from './validate_job_object';
const BUCKET_SPAN_COMPARE_FACTOR = 25;