diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 3337bfc8eb101..ca3b9bf99c535 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -116,6 +116,7 @@ module.exports = { }], '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/triple-slash-reference': ['error', { @@ -167,6 +168,7 @@ module.exports = { 'object-shorthand': 'error', 'one-var': [ 'error', 'never' ], 'prefer-const': 'error', + 'prefer-rest-params': 'error', 'quotes': ['error', 'double', { 'avoidEscape': true }], 'quote-props': ['error', 'consistent-as-needed'], 'radix': 'error', diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index a67593c02a593..13135196e3803 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -187,6 +187,7 @@ export default class ClusterManager { fromRoot('x-pack/legacy/plugins/reporting/.chromium'), fromRoot('x-pack/legacy/plugins/siem/cypress'), fromRoot('x-pack/legacy/plugins/apm/cypress'), + fromRoot('x-pack/legacy/plugins/apm/scripts'), fromRoot('x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes ]; diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index 946b3997a9712..c65ae3a0ec7b9 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -32,8 +32,9 @@ import { applyFiltersPopover, changeTimeFilter, extractTimeFilter, + IndexPatternsStart, } from '../../../../../../plugins/data/public'; -import { IndexPatternsStart } from '../../index_patterns'; + export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION'; interface ActionContext { @@ -74,7 +75,7 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { - return indexPatternsService.indexPatterns.get(filter.meta.index); + return indexPatternsService.indexPatterns.get(filter.meta.index!); }) ); diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 833d8c248f46a..bdfedbc2c81a8 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -48,4 +48,4 @@ export { /** @public static code */ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; -export { getFromSavedObject, getRoutes } from './index_patterns'; +export { getFromSavedObject, getRoutes, flattenHitWrapper } from './index_patterns'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index.ts b/src/legacy/core_plugins/data/public/index_patterns/index.ts index 496c4ba7b5b6f..74981165f3e47 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index.ts @@ -17,4 +17,21 @@ * under the License. */ -export * from './index_patterns_service'; +import { IFieldType, IIndexPattern, indexPatterns } from '../../../../../plugins/data/public'; + +const getFromSavedObject = indexPatterns.getFromSavedObject; +const getRoutes = indexPatterns.getRoutes; +const flattenHitWrapper = indexPatterns.flattenHitWrapper; + +export { getFromSavedObject, getRoutes, flattenHitWrapper }; +export { IFieldType as FieldType }; +export { IIndexPattern as StaticIndexPattern }; +export { + Field, + FieldListInterface, + IndexPattern, + IndexPatterns, + IndexPatternsStart, + IndexPatternsSetup, + IndexPatternsService, +} from '../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts b/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts index 3dcab22605c07..7a4c9f139c887 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/fetch_index_patterns.ts @@ -18,7 +18,7 @@ */ import { isEmpty } from 'lodash'; import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/public'; -import { getFromSavedObject } from '../../../index_patterns'; +import { getFromSavedObject } from '../../../'; export async function fetchIndexPatterns( savedObjectsClient: SavedObjectsClientContract, diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index 06c5caa04ba9a..0e12790d89fed 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -21,7 +21,7 @@ import { once } from 'lodash'; // @ts-ignore import { uiModules } from 'ui/modules'; -import { IndexPatterns } from '../index_patterns/index_patterns'; +import { IndexPatterns } from '../'; /** @internal */ export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx index e341b7c4a5a86..bad006aa8c7d5 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx @@ -21,7 +21,7 @@ import { mount } from 'enzyme'; import { IndexPattern } from 'ui/index_patterns'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { flattenHitWrapper } from '../../../../data/public/index_patterns/index_patterns/flatten_hit'; +import { flattenHitWrapper } from '../../../../data/public/'; import { DocViewTable } from './table'; // @ts-ignore diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 91364071579ab..af7aa3daf76cc 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -62,11 +62,11 @@ export default function (kibana) { uiExports: { hacks: [ + 'plugins/kibana/discover', 'plugins/kibana/dev_tools', ], savedObjectTypes: [ 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', - 'plugins/kibana/discover/saved_searches/saved_search_register', 'plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register', ], app: { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 797583362a8f8..42ecc0fea5f07 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -47,7 +47,7 @@ import { // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { DataStart } from '../../../data/public'; +import { DataStart, IndexPatterns } from '../../../data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -55,7 +55,7 @@ import { SharePluginStart } from '../../../../../plugins/share/public'; export interface RenderDeps { core: LegacyCoreStart; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; + indexPatterns: IndexPatterns; dataStart: DataStart; npDataStart: NpDataStart; navigation: NavigationStart; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index dcd25033e9d15..7972c7603d311 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -39,7 +39,7 @@ import { unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; -import { Query, SavedQuery } from '../../../../../plugins/data/public'; +import { Query, SavedQuery, IndexPatterns } from '../../../../../plugins/data/public'; import './dashboard_empty_screen_directive'; @@ -78,9 +78,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; getAppState: any; globalState: State; - indexPatterns: { - getDefault: () => Promise; - }; + indexPatterns: IndexPatterns; dashboardConfig: any; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; @@ -171,7 +169,7 @@ export class DashboardAppController { } else { indexPatterns.getDefault().then(defaultIndexPattern => { $scope.$evalAsync(() => { - $scope.indexPatterns = [defaultIndexPattern]; + $scope.indexPatterns = [defaultIndexPattern as IndexPattern]; }); }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js index 1abe8581c54c6..3f764cf576668 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js @@ -20,24 +20,26 @@ import { uiModules } from 'ui/modules'; import { capabilities } from 'ui/capabilities'; -uiModules.get('kibana') - .provider('dashboardConfig', () => { - let hideWriteControls = !capabilities.get().dashboard.showWriteControls; +export function dashboardConfigProvider() { + let hideWriteControls = !capabilities.get().dashboard.showWriteControls; + + return { + /** + * Part of the exposed plugin API - do not remove without careful consideration. + * @type {boolean} + */ + turnHideWriteControlsOn() { + hideWriteControls = true; + }, + $get() { + return { + getHideWriteControls() { + return hideWriteControls; + } + }; + } + }; +} - return { - /** - * Part of the exposed plugin API - do not remove without careful consideration. - * @type {boolean} - */ - turnHideWriteControlsOn() { - hideWriteControls = true; - }, - $get() { - return { - getHideWriteControls() { - return hideWriteControls; - } - }; - } - }; - }); +uiModules.get('kibana') + .provider('dashboardConfig', dashboardConfigProvider); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 9ac76bfcfe04e..92df04c536e43 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import '../../components/field_chooser/discover_field'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. @@ -32,8 +32,9 @@ describe('discoverField', function () { let $scope; let indexPattern; let $elem; - - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.inject(function (Private, $rootScope, $compile) { $elem = angular.element(` pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.inject(function (Private) { indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); })); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index bac56f008233c..34c6483349af6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import '../../components/field_chooser/field_chooser'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { SimpleSavedObject } from '../../../../../../../core/public'; @@ -70,8 +70,10 @@ describe('discover field chooser directives', function () { on-remove-field="removeField" > `); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('kibana', ($provide) => { + beforeEach(ngMock.module('app/discover', ($provide) => { $provide.decorator('config', ($delegate) => { // disable shortDots for these tests $delegate.get = _.wrap($delegate.get, function (origGet, name) { diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index b311dd8a34778..0d70bb993fac1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -20,7 +20,7 @@ @import 'embeddable/index'; // Doc Viewer -@import 'doc_viewer/index'; +@import 'components/doc_viewer/index'; // Context styles @import 'angular/context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context.js b/src/legacy/core_plugins/kibana/public/discover/angular/context.js index 58d1626ca4b14..989712a16b250 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context.js @@ -19,12 +19,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices, subscribeWithScope } from './../kibana_services'; +import { getAngularModule, getServices, subscribeWithScope } from './../kibana_services'; import './context_app'; import contextAppRouteTemplate from './context.html'; -import { getRootBreadcrumbs } from '../breadcrumbs'; -const { FilterBarQueryFilterProvider, uiRoutes, chrome } = getServices(); +import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; +import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; +const { chrome } = getServices(); const k7Breadcrumbs = $route => { const { indexPattern } = $route.current.locals; @@ -44,26 +45,33 @@ const k7Breadcrumbs = $route => { ]; }; -uiRoutes - // deprecated route, kept for compatibility - // should be removed in the future - .when('/context/:indexPatternId/:type/:id*', { - redirectTo: '/context/:indexPatternId/:id', - }) - .when('/context/:indexPatternId/:id*', { - controller: ContextAppRouteController, - k7Breadcrumbs, - controllerAs: 'contextAppRoute', - resolve: { - indexPattern: function ($route, indexPatterns) { - return indexPatterns.get($route.current.params.indexPatternId); +getAngularModule().config($routeProvider => { + $routeProvider + // deprecated route, kept for compatibility + // should be removed in the future + .when('/discover/context/:indexPatternId/:type/:id*', { + redirectTo: '/discover/context/:indexPatternId/:id', + }) + .when('/discover/context/:indexPatternId/:id*', { + controller: ContextAppRouteController, + k7Breadcrumbs, + controllerAs: 'contextAppRoute', + resolve: { + indexPattern: ($route, Promise) => { + const indexPattern = getServices().indexPatterns.get( + $route.current.params.indexPatternId + ); + return Promise.props({ ip: indexPattern }); + }, }, - }, - template: contextAppRouteTemplate, - }); + template: contextAppRouteTemplate, + }); +}); -function ContextAppRouteController($routeParams, $scope, AppState, config, indexPattern, Private) { - const queryFilter = Private(FilterBarQueryFilterProvider); +function ContextAppRouteController($routeParams, $scope, AppState, config, $route, getAppState, globalState) { + const filterManager = getServices().filterManager; + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const indexPattern = $route.current.locals.indexPattern.ip; this.state = new AppState(createDefaultAppState(config, indexPattern)); this.state.save(true); @@ -77,19 +85,20 @@ function ContextAppRouteController($routeParams, $scope, AppState, config, index () => this.state.save(true) ); - const updateSubsciption = subscribeWithScope($scope, queryFilter.getUpdates$(), { + const updateSubsciption = subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { - this.filters = _.cloneDeep(queryFilter.getFilters()); + this.filters = _.cloneDeep(filterManager.getFilters()); }, }); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { + filterStateManager.destroy(); updateSubsciption.unsubscribe(); }); this.anchorId = $routeParams.id; this.indexPattern = indexPattern; this.discoverUrl = chrome.navLinks.get('kibana:discover').url; - this.filters = _.cloneDeep(queryFilter.getFilters()); + this.filters = _.cloneDeep(filterManager.getFilters()); } function createDefaultAppState(config, indexPattern) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js index 4eb68c1bf50bc..8c6e53974ba36 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js @@ -19,13 +19,15 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; import { fetchAnchorProvider } from '../anchor'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchAnchor', function () { let fetchAnchor; @@ -35,11 +37,11 @@ describe('context app', function () { $provide.value('indexPatterns', createIndexPatternsStub()); })); - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createSearchSourceStub([ { _id: 'hit1' }, ]); - fetchAnchor = Private(fetchAnchorProvider); + fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); })); afterEach(() => { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js index ea6a8c092e242..a21e2117d1db6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; @@ -33,7 +34,8 @@ const ANCHOR_TIMESTAMP_1000 = (new Date(MS_PER_DAY * 1000)).toJSON(); const ANCHOR_TIMESTAMP_3000 = (new Date(MS_PER_DAY * 3000)).toJSON(); describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchPredecessors', function () { let fetchPredecessors; @@ -43,7 +45,7 @@ describe('context app', function () { $provide.value('indexPatterns', createIndexPatternsStub()); })); - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); fetchPredecessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { const anchor = { @@ -53,7 +55,7 @@ describe('context app', function () { sort: [timeValNr, tieBreakerValue] }; - return Private(fetchContextProvider).fetchSurroundingDocs( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'predecessors', indexPatternId, anchor, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js index 486c8ed9b410e..145de081f0d44 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; @@ -32,17 +33,14 @@ const ANCHOR_TIMESTAMP_3 = (new Date(MS_PER_DAY * 3)).toJSON(); const ANCHOR_TIMESTAMP_3000 = (new Date(MS_PER_DAY * 3000)).toJSON(); describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); describe('function fetchSuccessors', function () { let fetchSuccessors; let searchSourceStub; - beforeEach(ngMock.module(function createServiceStubs($provide) { - $provide.value('indexPatterns', createIndexPatternsStub()); - })); - - beforeEach(ngMock.inject(function createPrivateStubs(Private) { + beforeEach(ngMock.inject(function createPrivateStubs() { searchSourceStub = createContextSearchSourceStub([], '@timestamp'); fetchSuccessors = (indexPatternId, timeField, sortDir, timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { @@ -53,7 +51,7 @@ describe('context app', function () { sort: [timeValNr, tieBreakerValue] }; - return Private(fetchContextProvider).fetchSurroundingDocs( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'successors', indexPatternId, anchor, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js index 8c4cce810ca13..730b963d0474e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js @@ -19,17 +19,15 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../../kibana_services'; -const { SearchSource } = getServices(); -export function fetchAnchorProvider(indexPatterns) { +export function fetchAnchorProvider(indexPatterns, searchSource) { return async function fetchAnchor( indexPatternId, anchorId, sort ) { const indexPattern = await indexPatterns.get(indexPatternId); - const searchSource = new SearchSource() + searchSource .setParent(undefined) .setField('index', indexPattern) .setField('version', true) diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 68ccf56594e72..674f5616faa30 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SortDirection } from '../../../../../../../ui/public/courier'; -import { IndexPatterns, IndexPattern, getServices } from '../../../kibana_services'; -import { reverseSortDir } from './utils/sorting'; +import { IndexPatterns, IndexPattern, SearchSource } from '../../../kibana_services'; +import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; @@ -35,8 +34,6 @@ export interface EsHitRecord { } export type EsHitRecordList = EsHitRecord[]; -const { SearchSource } = getServices(); - const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts index 33f4454c18d40..eeae2aa2c5d0a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts @@ -16,10 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { reverseSortDir } from '../sorting'; -import { SortDirection } from '../../../../../../../../../ui/public/courier'; - -jest.mock('ui/new_platform'); +import { reverseSortDir, SortDirection } from '../sorting'; describe('function reverseSortDir', function() { test('reverse a given sort direction', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts index 47385aecb1937..4a0f531845f46 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts @@ -17,9 +17,13 @@ * under the License. */ -import { SortDirection } from '../../../../../../../../ui/public/courier'; import { IndexPattern } from '../../../../kibana_services'; +export enum SortDirection { + asc = 'asc', + desc = 'desc', +} + /** * The list of field names that are allowed for sorting, but not included in * index pattern fields. diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts index 579d9d95c6f71..55a378367392c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts @@ -16,11 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { getServices } from '../../../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../../../kibana_services'; import { ActionBar } from './action_bar'; -const { uiModules, wrapInI18nContext } = getServices(); - -uiModules.get('apps/context').directive('contextActionBar', function(reactDirective: any) { +getAngularModule().directive('contextActionBar', function(reactDirective: any) { return reactDirective(wrapInI18nContext(ActionBar)); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js index b88e54379f448..4a9480f9ea2ea 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js @@ -20,22 +20,22 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { toastNotifications } from '../../../kibana_services'; +import { getServices, SearchSource } from '../../../kibana_services'; import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; -import { QueryParameterActionsProvider } from '../query_parameters'; +import { getQueryParameterActions } from '../query_parameters'; import { FAILURE_REASONS, LOADING_STATUS } from './constants'; import { MarkdownSimple } from '../../../../../../kibana_react/public'; -export function QueryActionsProvider(Private, Promise) { - const fetchAnchor = Private(fetchAnchorProvider); - const { fetchSurroundingDocs } = Private(fetchContextProvider); +export function QueryActionsProvider(Promise) { + const fetchAnchor = fetchAnchorProvider(getServices().indexPatterns, new SearchSource()); + const { fetchSurroundingDocs } = fetchContextProvider(getServices().indexPatterns); const { setPredecessorCount, setQueryParameters, setSuccessorCount, - } = Private(QueryParameterActionsProvider); + } = getQueryParameterActions(); const setFailedStatus = (state) => (subject, details = {}) => ( state.loadingStatus[subject] = { @@ -79,7 +79,7 @@ export function QueryActionsProvider(Private, Promise) { }, (error) => { setFailedStatus(state)('anchor', { error }); - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.context.unableToLoadAnchorDocumentDescription', { defaultMessage: 'Unable to load the anchor document' }), @@ -128,7 +128,7 @@ export function QueryActionsProvider(Private, Promise) { }, (error) => { setFailedStatus(state)(type, { error }); - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.context.unableToLoadDocumentDescription', { defaultMessage: 'Unable to load documents' }), diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js index 5a445a65939ed..645ca32924ede 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js @@ -19,15 +19,16 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; import { createIndexPatternsStub } from '../../api/__tests__/_stubs'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { npStart } from 'ui/new_platform'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); - + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); beforeEach(ngMock.module(function createServiceStubs($provide) { $provide.value('indexPatterns', createIndexPatternsStub()); })); @@ -35,9 +36,8 @@ describe('context app', function () { describe('action addFilter', function () { let addFilter; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - Private.stub(getServices().FilterBarQueryFilterProvider); - addFilter = Private(QueryParameterActionsProvider).addFilter; + beforeEach(ngMock.inject(function createPrivateStubs() { + addFilter = getQueryParameterActions().addFilter; })); it('should pass the given arguments to the filterManager', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js index 7c1fa320ae17b..a8bef6fe75c79 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js @@ -19,19 +19,21 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; - +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setPredecessorCount', function () { let setPredecessorCount; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setPredecessorCount = Private(QueryParameterActionsProvider).setPredecessorCount; + beforeEach(ngMock.inject(function createPrivateStubs() { + setPredecessorCount = getQueryParameterActions().setPredecessorCount; })); it('should set the predecessorCount to the given value', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js index 853c5726b3da5..a43a8a11a7bf8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js @@ -19,19 +19,22 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setQueryParameters', function () { let setQueryParameters; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setQueryParameters = Private(QueryParameterActionsProvider).setQueryParameters; + beforeEach(ngMock.inject(function createPrivateStubs() { + setQueryParameters = getQueryParameterActions().setQueryParameters; })); it('should update the queryParameters with valid properties from the given object', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js index d63bf2ecf53af..4bbd462aaa4b0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js @@ -19,19 +19,22 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { QueryParameterActionsProvider } from '../actions'; +import { getQueryParameterActions } from '../actions'; describe('context app', function () { - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(() => pluginInstance.initializeServices(true)); + beforeEach(ngMock.module('app/discover')); describe('action setSuccessorCount', function () { let setSuccessorCount; - beforeEach(ngMock.inject(function createPrivateStubs(Private) { - setSuccessorCount = Private(QueryParameterActionsProvider).setSuccessorCount; + beforeEach(ngMock.inject(function createPrivateStubs() { + setSuccessorCount = getQueryParameterActions().setSuccessorCount; })); it('should set the successorCount to the given value', function () { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 10fe6c0e2eda1..28b35a1b81a7b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -18,8 +18,8 @@ */ import _ from 'lodash'; +import { getServices } from '../../../kibana_services'; import { generateFilters } from '../../../../../../../../plugins/data/public'; -import { npStart } from 'ui/new_platform'; import { MAX_CONTEXT_SIZE, @@ -28,8 +28,8 @@ import { } from './constants'; -export function QueryParameterActionsProvider(indexPatterns) { - const { filterManager } = npStart.plugins.data.query; +export function getQueryParameterActions() { + const filterManager = getServices().filterManager; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( @@ -62,7 +62,7 @@ export function QueryParameterActionsProvider(indexPatterns) { const indexPatternId = state.queryParameters.indexPatternId; const newFilters = generateFilters(filterManager, field, values, operation, indexPatternId); filterManager.addFilters(newFilters); - const indexPattern = await indexPatterns.get(indexPatternId); + const indexPattern = await getServices().indexPatterns.get(indexPatternId); indexPattern.popularizeField(field.name, 1); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js index 3e7f47668df59..14be90a3f61a4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js @@ -17,7 +17,7 @@ * under the License. */ -export { QueryParameterActionsProvider } from './actions'; +export { getQueryParameterActions } from './actions'; export { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js index c9856ad794952..4609317712379 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js @@ -18,13 +18,13 @@ */ import _ from 'lodash'; -import { getServices, callAfterBindingsWorkaround } from './../kibana_services'; +import { getServices, callAfterBindingsWorkaround, getAngularModule } from './../kibana_services'; import contextAppTemplate from './context_app.html'; import './context/components/action_bar'; import { getFirstSortableField } from './context/api/utils/sorting'; import { createInitialQueryParametersState, - QueryParameterActionsProvider, + getQueryParameterActions, QUERY_PARAMETER_KEYS, } from './context/query_parameters'; import { @@ -34,17 +34,12 @@ import { QueryActionsProvider, } from './context/query'; -const { uiModules, timefilter } = getServices(); +const { timefilter } = getServices(); // load directives import '../../../../data/public/legacy'; -const module = uiModules.get('apps/context', [ - 'elasticsearch', - 'kibana', - 'kibana/config', - 'ngRoute', -]); +const module = getAngularModule(); module.directive('contextApp', function ContextApp() { return { @@ -67,7 +62,7 @@ module.directive('contextApp', function ContextApp() { }); function ContextAppController($scope, config, Private) { - const queryParameterActions = Private(QueryParameterActionsProvider); + const queryParameterActions = getQueryParameterActions(); const queryActions = Private(QueryActionsProvider); timefilter.disableAutoRefreshSelector(); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx index ab336396b5bed..496e1cf375588 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx @@ -70,7 +70,7 @@ export class DiscoverHistogram extends Component this.setState({ chartsTheme })); + .subscribe((chartsTheme: EuiChartThemeType['theme']) => this.setState({ chartsTheme })); } componentWillUnmount() { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js index b0b766478450f..f1e783c56263e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js @@ -23,10 +23,9 @@ import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern'; import { DiscoverHistogram } from './histogram'; -import { getServices } from '../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../kibana_services'; -const { wrapInI18nContext, uiModules } = getServices(); -const app = uiModules.get('apps/discover', ['react']); +const app = getAngularModule(); app.directive('discoverNoResults', reactDirective => reactDirective(wrapInI18nContext(DiscoverNoResults)) diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 0e92d048a65a9..7af7ee235a166 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -23,7 +23,6 @@ import { Subscription } from 'rxjs'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; -import '../saved_searches/saved_searches'; import '../components/field_chooser/field_chooser'; // doc table @@ -33,7 +32,7 @@ import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_sour import * as columnActions from './doc_table/actions/columns'; import indexTemplate from './discover.html'; -import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; +import { showOpenSearchPanel } from '../components/top_nav/show_open_search_panel'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import '../components/fetch_error'; import { getPainlessError } from './get_painless_error'; @@ -57,28 +56,31 @@ import { vislibSeriesResponseHandlerProvider, Vis, SavedObjectSaveModal, + getAngularModule, ensureDefaultIndexPattern, } from '../kibana_services'; const { core, chrome, + data, docTitle, - FilterBarQueryFilterProvider, + filterManager, + State, share, - StateProvider, timefilter, - npData, toastNotifications, - uiModules, - uiRoutes, -} = getServices(); + uiSettings +} = getServices(); -import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; +import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; +import { start as dataLP } from '../../../../data/public/legacy'; import { generateFilters } from '../../../../../../plugins/data/public'; -import { start as data } from '../../../../data/public/legacy'; +import { getIndexPatternId } from '../helpers/get_index_pattern_id'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../../ui/public/timefilter/setup_router'; +import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; -const savedQueryService = npData.query.savedQueries; +const { savedQueryService } = data.query.savedQueries; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -86,20 +88,20 @@ const fetchStatuses = { COMPLETE: 'complete', }; -const app = uiModules.get('apps/discover', [ - 'kibana/url', - 'kibana/index_patterns' -]); +const app = getAngularModule(); +app.run((globalState, $rootScope) => {registerTimefilterWithGlobalStateFactory( + timefilter, + globalState, + $rootScope +); +}); -uiRoutes - .defaults(/^\/discover(\/|$)/, { +app.config($routeProvider => { + const defaults = { + requireDefaultIndex: true, requireUICapability: 'discover.show', k7Breadcrumbs: ($route, $injector) => - $injector.invoke( - $route.current.params.id - ? getSavedSearchBreadcrumbs - : getRootBreadcrumbs - ), + $injector.invoke($route.current.params.id ? getSavedSearchBreadcrumbs : getRootBreadcrumbs), badge: uiCapabilities => { if (uiCapabilities.discover.save) { return undefined; @@ -112,21 +114,21 @@ uiRoutes tooltip: i18n.translate('kbn.discover.badge.readOnly.tooltip', { defaultMessage: 'Unable to save searches', }), - iconType: 'glasses' + iconType: 'glasses', }; - } - }) - .when('/discover/:id?', { + }, + }; + $routeProvider.when('/discover/:id?', { + ...defaults, template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) { - const State = Private(StateProvider); + savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { + const indexPatterns = dataLP.indexPatterns.indexPatterns; const savedSearchId = $route.current.params.id; - - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(core, dataLP, $rootScope, kbnUrl).then(() => { return Promise.props({ - ip: indexPatterns.getCache().then((savedObjects) => { + ip: indexPatterns.getCache().then((indexPatternList) => { /** * In making the indexPattern modifiable it was placed in appState. Unfortunately, * the load order of AppState conflicts with the load order of many other things @@ -138,19 +140,16 @@ uiRoutes */ const state = new State('_a', {}); - const specified = !!state.index; - const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; - const id = exists ? state.index : config.get('defaultIndex'); + const id = getIndexPatternId(state.index, indexPatternList, uiSettings.get('defaultIndex')); state.destroy(); - return Promise.props({ - list: savedObjects, + list: indexPatternList, loaded: indexPatterns.get(id), stateVal: state.index, - stateValFound: specified && exists + stateValFound: !!state.index && id === state.index, }); }), - savedSearch: savedSearches.get(savedSearchId) + savedSearch: getServices().getSavedSearchById(savedSearchId, kbnUrl) .then((savedSearch) => { if (savedSearchId) { chrome.recentlyAccessed.add( @@ -169,6 +168,7 @@ uiRoutes }, } }); +}); app.directive('discoverApp', function () { return { @@ -190,12 +190,14 @@ function discoverController( config, kbnUrl, localStorage, - uiCapabilities + uiCapabilities, + getAppState, + globalState, ) { const responseHandler = vislibSeriesResponseHandlerProvider().handler; const getUnhashableStates = Private(getUnhashableStatesProvider); + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); - const queryFilter = Private(FilterBarQueryFilterProvider); const inspectorAdapters = { requests: new RequestAdapter() @@ -235,6 +237,7 @@ function discoverController( if (abortController) abortController.abort(); savedSearch.destroy(); subscriptions.unsubscribe(); + filterStateManager.destroy(); }); const $appStatus = $scope.appStatus = this.appStatus = { @@ -393,7 +396,7 @@ function discoverController( $scope.searchSource.setParent(timeRangeSearchSource); const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; - docTitle.change(`Discover${pageTitleSuffix}`); + chrome.docTitle.change(`Discover${pageTitleSuffix}`); const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); @@ -413,16 +416,16 @@ function discoverController( const $state = $scope.state = new AppState(getStateDefaults()); - $scope.filters = queryFilter.getFilters(); + $scope.filters = filterManager.getFilters(); $scope.screenTitle = savedSearch.title; $scope.onFiltersUpdated = filters => { - // The filters will automatically be set when the queryFilter emits an update event (see below) - queryFilter.setFilters(filters); + // The filters will automatically be set when the filterManager emits an update event (see below) + filterManager.setFilters(filters); }; const getFieldCounts = async () => { - // the field counts aren't set until we have the data back, + // the field counts aren't set until we have the dataLP back, // so we wait for the fetch to be done before proceeding if ($scope.fetchStatus === fetchStatuses.COMPLETE) { return $scope.fieldCounts; @@ -578,22 +581,22 @@ function discoverController( if (!angular.equals(sort, currentSort)) $scope.fetch(); }); - // update data source when filters update - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + // update dataLP source when filters update + subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { - $scope.filters = queryFilter.getFilters(); + $scope.filters = filterManager.getFilters(); $scope.updateDataSource().then(function () { $state.save(); }); } })); - // fetch data when filters fire fetch event - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + // fetch dataLP when filters fire fetch event + subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: $scope.fetch })); - // update data source when hitting forward/back and the query changes + // update dataLP source when hitting forward/back and the query changes $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.indexOf('query') >= 0) $scope.fetch(); }); @@ -633,7 +636,7 @@ function discoverController( let prev = {}; const status = { UNINITIALIZED: 'uninitialized', - LOADING: 'loading', // initial data load + LOADING: 'loading', // initial dataLP load READY: 'ready', // results came back NO_RESULTS: 'none' // no results came back }; @@ -704,7 +707,7 @@ function discoverController( savedSearchTitle: savedSearch.title, } }), - 'data-test-subj': 'saveSearchSuccess', + 'dataLP-test-subj': 'saveSearchSuccess', }); if (savedSearch.id !== $route.current.params.id) { @@ -765,7 +768,7 @@ function discoverController( } else { toastNotifications.addError(error, { title: i18n.translate('kbn.discover.errorLoadingData', { - defaultMessage: 'Error loading data', + defaultMessage: 'Error loading dataLP', }), }); } @@ -813,10 +816,10 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { - defaultMessage: 'Data', + defaultMessage: 'dataLP', }); const description = i18n.translate('kbn.discover.inspectorRequestDescription', { - defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', + defaultMessage: 'This request queries Elasticsearch to fetch the dataLP for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); @@ -875,7 +878,7 @@ function discoverController( .setField('size', $scope.opts.sampleSize) .setField('sort', getSortForSearchSource($state.sort, indexPattern)) .setField('query', !$state.query ? null : $state.query) - .setField('filter', queryFilter.getFilters()); + .setField('filter', filterManager.getFilters()); }); $scope.setSortOrder = function setSortOrder(sortPair) { @@ -885,8 +888,8 @@ function discoverController( // TODO: On array fields, negating does not negate the combination, rather all terms $scope.filterQuery = function (field, values, operation) { $scope.indexPattern.popularizeField(field, 1); - const newFilters = generateFilters(queryFilter, field, values, operation, $scope.indexPattern.id); - return queryFilter.addFilters(newFilters); + const newFilters = generateFilters(filterManager, field, values, operation, $scope.indexPattern.id); + return filterManager.addFilters(newFilters); }; $scope.addColumn = function addColumn(columnName) { @@ -933,7 +936,7 @@ function discoverController( query: '', language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }; - queryFilter.removeAll(); + filterManager.removeAll(); $state.save(); $scope.fetch(); }; @@ -941,8 +944,7 @@ function discoverController( const updateStateFromSavedQuery = (savedQuery) => { $state.query = savedQuery.attributes.query; $state.save(); - - queryFilter.setFilters(savedQuery.attributes.filters || []); + filterManager.setFilters(savedQuery.attributes.filters || []); if (savedQuery.attributes.timefilter) { timefilter.setTime({ @@ -972,7 +974,6 @@ function discoverController( $scope.savedQuery = undefined; return; } - if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { $scope.$evalAsync(() => { @@ -983,6 +984,7 @@ function discoverController( } }); + async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages if (!$scope.opts.timefield) return; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts index e6c890c9a66a2..af9556656afab 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts @@ -16,13 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { getServices, IndexPatterns } from '../kibana_services'; +import { getAngularModule, wrapInI18nContext, getServices } from '../kibana_services'; // @ts-ignore -import { getRootBreadcrumbs } from '../breadcrumbs'; +import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; import html from './doc.html'; -import { Doc } from '../doc/doc'; -const { uiRoutes, uiModules, wrapInI18nContext, timefilter } = getServices(); -uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: any) { +import { Doc } from '../components/doc/doc'; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +const { timefilter } = getServices(); +const app = getAngularModule(); +app.directive('discoverDoc', function(reactDirective: any) { return reactDirective( wrapInI18nContext(Doc), [ @@ -36,28 +42,28 @@ uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: ); }); -uiRoutes - // the old, pre 8.0 route, no longer used, keep it to stay compatible - // somebody might have bookmarked his favorite log messages - .when('/doc/:indexPattern/:index/:type', { - redirectTo: '/doc/:indexPattern/:index', - }) - // the new route, es 7 deprecated types, es 8 removed them - .when('/doc/:indexPattern/:index', { - controller: ($scope: any, $route: any, es: any, indexPatterns: IndexPatterns) => { - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - $scope.esClient = es; - $scope.id = $route.current.params.id; - $scope.index = $route.current.params.index; - $scope.indexPatternId = $route.current.params.indexPattern; - $scope.indexPatternService = indexPatterns; - }, - template: html, - k7Breadcrumbs: ($route: any) => [ - ...getRootBreadcrumbs(), - { - text: `${$route.current.params.index}#${$route.current.params.id}`, +app.config(($routeProvider: any) => { + $routeProvider + .when('/discover/doc/:indexPattern/:index/:type', { + redirectTo: '/discover/doc/:indexPattern/:index', + }) + // the new route, es 7 deprecated types, es 8 removed them + .when('/discover/doc/:indexPattern/:index', { + controller: ($scope: LazyScope, $route: any, es: any) => { + timefilter.disableAutoRefreshSelector(); + timefilter.disableTimeRangeSelector(); + $scope.esClient = es; + $scope.id = $route.current.params.id; + $scope.index = $route.current.params.index; + $scope.indexPatternId = $route.current.params.indexPattern; + $scope.indexPatternService = getServices().indexPatterns; }, - ], - }); + template: html, + k7Breadcrumbs: ($route: any) => [ + ...getRootBreadcrumbs(), + { + text: `${$route.current.params.index}#${$route.current.params.id}`, + }, + ], + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js index 417d521dd44ed..2c6718e44894f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; -import '..'; +import { pluginInstance } from 'plugins/kibana/discover/index'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import hits from 'fixtures/real_hits'; @@ -65,8 +65,8 @@ const destroy = function () { describe('docTable', function () { let $elem; - - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach(function () { $elem = angular.element(` pluginInstance.initializeServices(true)); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach( ngMock.inject(function (_config_, $rootScope, Private) { config = _config_; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts similarity index 78% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts index 7462de544dbce..3a037971a1253 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts @@ -16,18 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { getServices } from '../../../../kibana_services'; +import { wrapInI18nContext } from '../../../../kibana_services'; import { ToolBarPagerText } from './tool_bar_pager_text'; import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; -const { wrapInI18nContext, uiModules } = getServices(); - -const app = uiModules.get('kibana'); - -app.directive('toolBarPagerText', function (reactDirective) { +export function createToolBarPagerTextDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(ToolBarPagerText)); -}); +} -app.directive('toolBarPagerButtons', function (reactDirective) { +export function createToolBarPagerButtonsDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(ToolBarPagerButtons)); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts index f447c54507729..055f14f164476 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts @@ -17,11 +17,10 @@ * under the License. */ import { wrapInI18nContext } from 'ui/i18n'; -import { getServices } from '../../../kibana_services'; +import { IUiSettingsClient } from 'kibana/public'; import { TableHeader } from './table_header/table_header'; -const module = getServices().uiModules.get('app/discover'); -module.directive('kbnTableHeader', function(reactDirective: any, config: any) { +export function createTableHeaderDirective(reactDirective: any, config: IUiSettingsClient) { return reactDirective( wrapInI18nContext(TableHeader), [ @@ -40,4 +39,4 @@ module.directive('kbnTableHeader', function(reactDirective: any, config: any) { isShortDots: config.get('shortDots:enable'), } ); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts similarity index 86% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts index 6f5a94442e977..8ff4ab46ef532 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts @@ -19,32 +19,34 @@ import _ from 'lodash'; import $ from 'jquery'; +import { IUiSettingsClient } from 'kibana/public'; +// @ts-ignore import rison from 'rison-node'; import '../../doc_viewer'; +// @ts-ignore import { noWhiteSpace } from '../../../../../common/utils/no_white_space'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; -import { getServices } from '../../../kibana_services'; + import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; import cellTemplateHtml from '../components/table_row/cell.html'; import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; import { esFilters } from '../../../../../../../../plugins/data/public'; -const module = getServices().uiModules.get('app/discover'); - // guesstimate at the minimum number of chars wide cells in the table should be const MIN_LINE_LENGTH = 20; -/** - * kbnTableRow directive - * - * Display a row in the table - * ``` - * - * ``` - */ -module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl, config) { +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +export function createTableRowDirective( + $compile: ng.ICompileService, + $httpParamSerializer: any, + kbnUrl: any, + config: IUiSettingsClient +) { const cellTemplate = _.template(noWhiteSpace(cellTemplateHtml)); const truncateByHeightTemplate = _.template(noWhiteSpace(truncateByHeightTemplateHtml)); @@ -59,18 +61,18 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl onAddColumn: '=?', onRemoveColumn: '=?', }, - link: function ($scope, $el) { + link: ($scope: LazyScope, $el: JQuery) => { $el.after(''); $el.empty(); // when we compile the details, we use this $scope - let $detailsScope; + let $detailsScope: LazyScope; // when we compile the toggle button in the summary, we use this $scope let $toggleScope; // toggle display of the rows details, a full list of the fields from each row - $scope.toggleRow = function () { + $scope.toggleRow = () => { const $detailsTr = $el.next(); $scope.open = !$scope.open; @@ -99,18 +101,18 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl $compile($detailsTr)($detailsScope); }; - $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight', '[]columns'], function () { - createSummaryRow($scope.row, $scope.row._id); + $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight', '[]columns'], () => { + createSummaryRow($scope.row); }); - $scope.inlineFilter = function inlineFilter($event, type) { + $scope.inlineFilter = function inlineFilter($event: any, type: string) { const column = $($event.target).data().column; const field = $scope.indexPattern.fields.getByName(column); $scope.filter(field, $scope.flattenedRow[column], type); }; $scope.getContextAppHref = () => { - const path = kbnUrl.eval('#/context/{{ indexPattern }}/{{ anchorId }}', { + const path = kbnUrl.eval('#/discover/context/{{ indexPattern }}/{{ anchorId }}', { anchorId: $scope.row._id, indexPattern: $scope.indexPattern.id, }); @@ -124,7 +126,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl }; // create a tr element that lists the value for each *column* - function createSummaryRow(row) { + function createSummaryRow(row: any) { const indexPattern = $scope.indexPattern; $scope.flattenedRow = indexPattern.flattenHit(row); @@ -145,7 +147,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl ); } - $scope.columns.forEach(function (column) { + $scope.columns.forEach(function(column: any) { const isFilterable = $scope.flattenedRow[column] !== undefined && mapping(column) && @@ -164,11 +166,11 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl }); let $cells = $el.children(); - newHtmls.forEach(function (html, i) { + newHtmls.forEach(function(html, i) { const $cell = $cells.eq(i); if ($cell.data('discover:html') === html) return; - const reuse = _.find($cells.slice(i + 1), function (cell) { + const reuse = _.find($cells.slice(i + 1), function(cell: any) { return $.data(cell, 'discover:html') === html; }); @@ -202,7 +204,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl /** * Fill an element with the value of a field */ - function _displayField(row, fieldName, truncate) { + function _displayField(row: any, fieldName: string, truncate = false) { const indexPattern = $scope.indexPattern; const text = indexPattern.formatField(row, fieldName); @@ -216,4 +218,4 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl } }, }; -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html index 5c8785e8dc5f9..d149a9023816a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html @@ -30,7 +30,7 @@ @@ -48,5 +48,5 @@ on-remove-column="onRemoveColumn" > - + diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js deleted file mode 100644 index 72943671fec22..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import html from './doc_table.html'; -import './infinite_scroll'; -import './components/table_header'; -import './components/table_row'; -import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; -import { getServices } from '../../kibana_services'; -import './components/pager'; -import './lib/pager'; - -import { getLimitedSearchResultsMessage } from './doc_table_strings'; - -const { uiModules } = getServices(); - -uiModules.get('app/discover') - .directive('docTable', function (config, getAppState, pagerFactory, $filter) { - return { - restrict: 'E', - template: html, - scope: { - sorting: '=', - columns: '=', - hits: '=', - totalHitCount: '=', - indexPattern: '=', - isLoading: '=?', - infiniteScroll: '=?', - filter: '=?', - filters: '=?', - minimumVisibleRows: '=?', - onAddColumn: '=?', - onChangeSortOrder: '=?', - onMoveColumn: '=?', - onRemoveColumn: '=?', - inspectorAdapters: '=?', - }, - link: function ($scope, $el) { - $scope.$watch('minimumVisibleRows', (minimumVisibleRows) => { - $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); - }); - - $scope.persist = { - sorting: $scope.sorting, - columns: $scope.columns - }; - - const limitTo = $filter('limitTo'); - const calculateItemsOnPage = () => { - $scope.pager.setTotalItems($scope.hits.length); - $scope.pageOfItems = limitTo($scope.hits, $scope.pager.pageSize, $scope.pager.startIndex); - }; - - $scope.limitedResultsWarning = getLimitedSearchResultsMessage(config.get('discover:sampleSize')); - - $scope.addRows = function () { - $scope.limit += 50; - }; - - // This exists to fix the problem of an empty initial column list not playing nice with watchCollection. - $scope.$watch('columns', function (columns) { - if (columns.length !== 0) return; - - const $state = getAppState(); - $scope.columns.push('_source'); - if ($state) $state.replace(); - }); - - $scope.$watchCollection('columns', function (columns, oldColumns) { - if (oldColumns.length === 1 && oldColumns[0] === '_source' && $scope.columns.length > 1) { - _.pull($scope.columns, '_source'); - } - - if ($scope.columns.length === 0) $scope.columns.push('_source'); - }); - - $scope.$watch('hits', hits => { - if (!hits) return; - - // Reset infinite scroll limit - $scope.limit = 50; - - if (hits.length === 0) { - dispatchRenderComplete($el[0]); - } - - if ($scope.infiniteScroll) return; - $scope.pager = pagerFactory.create(hits.length, 50, 1); - calculateItemsOnPage(); - }); - - $scope.pageOfItems = []; - $scope.onPageNext = () => { - $scope.pager.nextPage(); - calculateItemsOnPage(); - }; - - $scope.onPagePrevious = () => { - $scope.pager.previousPage(); - calculateItemsOnPage(); - }; - - $scope.shouldShowLimitedResultsWarning = () => ( - !$scope.pager.hasNextPage && $scope.pager.totalItems < $scope.totalHitCount - ); - } - }; - }); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts new file mode 100644 index 0000000000000..92ebc24c6e378 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { IUiSettingsClient } from 'kibana/public'; +import html from './doc_table.html'; +import './infinite_scroll'; +import './components/table_header'; +import './components/table_row'; +import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; +import './components/pager'; +import './lib/pager'; +// @ts-ignore +import { getLimitedSearchResultsMessage } from './doc_table_strings'; + +interface LazyScope extends ng.IScope { + [key: string]: any; +} + +export function createDocTableDirective( + config: IUiSettingsClient, + getAppState: any, + pagerFactory: any, + $filter: any +) { + return { + restrict: 'E', + template: html, + scope: { + sorting: '=', + columns: '=', + hits: '=', + totalHitCount: '=', + indexPattern: '=', + isLoading: '=?', + infiniteScroll: '=?', + filter: '=?', + filters: '=?', + minimumVisibleRows: '=?', + onAddColumn: '=?', + onChangeSortOrder: '=?', + onMoveColumn: '=?', + onRemoveColumn: '=?', + inspectorAdapters: '=?', + }, + link: ($scope: LazyScope, $el: JQuery) => { + $scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => { + $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); + }); + + $scope.persist = { + sorting: $scope.sorting, + columns: $scope.columns, + }; + + const limitTo = $filter('limitTo'); + const calculateItemsOnPage = () => { + $scope.pager.setTotalItems($scope.hits.length); + $scope.pageOfItems = limitTo($scope.hits, $scope.pager.pageSize, $scope.pager.startIndex); + }; + + $scope.limitedResultsWarning = getLimitedSearchResultsMessage( + config.get('discover:sampleSize') + ); + + $scope.addRows = function() { + $scope.limit += 50; + }; + + // This exists to fix the problem of an empty initial column list not playing nice with watchCollection. + $scope.$watch('columns', function(columns: string[]) { + if (columns.length !== 0) return; + + const $state = getAppState(); + $scope.columns.push('_source'); + if ($state) $state.replace(); + }); + + $scope.$watchCollection('columns', function(columns: string[], oldColumns: string[]) { + if (oldColumns.length === 1 && oldColumns[0] === '_source' && $scope.columns.length > 1) { + _.pull($scope.columns, '_source'); + } + + if ($scope.columns.length === 0) $scope.columns.push('_source'); + }); + + $scope.$watch('hits', (hits: any) => { + if (!hits) return; + + // Reset infinite scroll limit + $scope.limit = 50; + + if (hits.length === 0) { + dispatchRenderComplete($el[0]); + } + + if ($scope.infiniteScroll) return; + $scope.pager = pagerFactory.create(hits.length, 50, 1); + calculateItemsOnPage(); + }); + + $scope.pageOfItems = []; + $scope.onPageNext = () => { + $scope.pager.nextPage(); + calculateItemsOnPage(); + }; + + $scope.onPagePrevious = () => { + $scope.pager.previousPage(); + calculateItemsOnPage(); + }; + + $scope.shouldShowLimitedResultsWarning = () => + !$scope.pager.hasNextPage && $scope.pager.totalItems < $scope.totalHitCount; + }, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts similarity index 68% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts index bf12deeb6b05f..1a8ad372bbb8a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts @@ -18,30 +18,32 @@ */ import $ from 'jquery'; -import { getServices } from '../../kibana_services'; -const module = getServices().uiModules.get('app/discover'); +interface LazyScope extends ng.IScope { + [key: string]: any; +} -module.directive('kbnInfiniteScroll', function () { +export function createInfiniteScrollDirective() { return { restrict: 'E', scope: { - more: '=' + more: '=', }, - link: function ($scope, $element) { + link: ($scope: LazyScope, $element: JQuery) => { const $window = $(window); - let checkTimer; + let checkTimer: any; function onScroll() { if (!$scope.more) return; - const winHeight = $window.height(); - const winBottom = winHeight + $window.scrollTop(); - const elTop = $element.offset().top; + const winHeight = Number($window.height()); + const winBottom = Number(winHeight) + Number($window.scrollTop()); + const offset = $element.offset(); + const elTop = offset ? offset.top : 0; const remaining = elTop - winBottom; - if (remaining <= winHeight * 0.50) { - $scope[$scope.$$phase ? '$eval' : '$apply'](function () { + if (remaining <= winHeight * 0.5) { + $scope[$scope.$$phase ? '$eval' : '$apply'](function() { $scope.more(); }); } @@ -49,18 +51,18 @@ module.directive('kbnInfiniteScroll', function () { function scheduleCheck() { if (checkTimer) return; - checkTimer = setTimeout(function () { + checkTimer = setTimeout(function() { checkTimer = null; onScroll(); }, 50); } $window.on('scroll', scheduleCheck); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', function() { clearTimeout(checkTimer); $window.off('scroll', scheduleCheck); }); scheduleCheck(); - } + }, }; -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts similarity index 83% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts index 5d488fab0c87f..fe576b63568dd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts @@ -16,16 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - -import { getServices } from '../../../../kibana_services'; +// @ts-ignore import { Pager } from './pager'; -const app = getServices().uiModules.get('kibana'); - -app.factory('pagerFactory', () => { +export function createPagerFactory() { return { - create(...args) { + create(...args: unknown[]) { return new Pager(...args); - } + }, }; -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts index c13c354528413..6ba47b839563b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts @@ -17,13 +17,9 @@ * under the License. */ -// @ts-ignore -import { getServices } from '../kibana_services'; -import { DocViewer } from '../doc_viewer/doc_viewer'; +import { DocViewer } from '../components/doc_viewer/doc_viewer'; -const { uiModules } = getServices(); - -uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { +export function createDocViewerDirective(reactDirective: any) { return reactDirective( DocViewer, [ @@ -46,4 +42,4 @@ uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { }, } ); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/application.ts b/src/legacy/core_plugins/kibana/public/discover/application.ts new file mode 100644 index 0000000000000..83f4a5962e3cd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/application.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular from 'angular'; + +/** + * Here's where Discover's inner angular is mounted and rendered + */ +export async function renderApp(moduleName: string, element: HTMLElement) { + await import('./angular'); + const $injector = mountDiscoverApp(moduleName, element); + return () => $injector.get('$rootScope').$destroy(); +} + +function mountDiscoverApp(moduleName: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + const appWrapper = document.createElement('div'); + appWrapper.setAttribute('ng-view', ''); + mountpoint.appendChild(appWrapper); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + $injector.get('globalState'); + element.appendChild(mountpoint); + return $injector; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx index ee80f29c053dc..4df56483fa5c6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx @@ -28,7 +28,7 @@ jest.mock('../doc_viewer/doc_viewer', () => ({ DocViewer: 'test', })); -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ metadata: { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx index 0e0e6ed110ca6..85308d9c7e03e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx @@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; -import { IndexPatterns, ElasticSearchHit, getServices } from '../kibana_services'; +import { IndexPatterns, ElasticSearchHit, getServices } from '../../kibana_services'; export interface ElasticSearchResult { hits: { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts rename to src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts index 538fbed821f00..20bffe829de16 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts @@ -17,7 +17,7 @@ * under the License. */ import { useEffect, useState } from 'react'; -import { ElasticSearchHit, IndexPattern } from '../kibana_services'; +import { ElasticSearchHit, IndexPattern } from '../../kibana_services'; import { DocProps } from './doc'; export enum ElasticRequestState { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx index 12473b25802f2..158ed4ccc7759 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx @@ -28,7 +28,7 @@ import { getDocViewsSorted as mockGetDocViewsSorted, } from 'ui/registry/doc_views'; -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ docViewsRegistry: { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx similarity index 90% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx index aa737ebd8dcf1..a2d58439ad031 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx @@ -17,8 +17,9 @@ * under the License. */ import React from 'react'; +import { DocView } from 'ui/registry/doc_views_types'; import { EuiTabbedContent } from '@elastic/eui'; -import { getServices, DocViewRenderProps } from '../kibana_services'; +import { getServices, DocViewRenderProps } from '../../kibana_services'; import { DocViewerTab } from './doc_viewer_tab'; /** @@ -31,7 +32,7 @@ export function DocViewer(renderProps: DocViewRenderProps) { const { docViewsRegistry } = getServices(); const tabs = docViewsRegistry .getDocViewsSorted(renderProps.hit) - .map(({ title, render, component }, idx) => { + .map(({ title, render, component }: DocView, idx: number) => { return { id: title, name: title, diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx index 5fa2d24dfa04c..476d7cef159fb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { DocViewRenderTab } from './doc_viewer_render_tab'; -import { DocViewRenderProps } from '../kibana_services'; +import { DocViewRenderProps } from '../../kibana_services'; test('Mounting and unmounting DocViewerRenderTab', () => { const unmountFn = jest.fn(); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx index 750ef6b6061e1..8ac11caefff90 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useRef, useEffect } from 'react'; -import { DocViewRenderFn, DocViewRenderProps } from '../kibana_services'; +import { DocViewRenderFn, DocViewRenderProps } from '../../kibana_services'; interface Props { render: DocViewRenderFn; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx index 3721ba5818d41..19558129eae8d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { DocViewRenderProps, DocViewRenderFn } from '../kibana_services'; +import { DocViewRenderProps, DocViewRenderFn } from '../../kibana_services'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx similarity index 79% rename from src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js rename to src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx index 612ca860f8031..8f67c1952f998 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx @@ -19,10 +19,18 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; -import { getServices } from '../../kibana_services'; -const { uiModules, wrapInI18nContext, chrome } = getServices(); +import { getAngularModule, wrapInI18nContext, getServices } from '../../kibana_services'; -const DiscoverFetchError = ({ fetchError }) => { +interface Props { + fetchError: { + lang: string; + script: string; + message: string; + error: string; + }; +} + +const DiscoverFetchError = ({ fetchError }: Props) => { if (!fetchError) { return null; } @@ -30,7 +38,9 @@ const DiscoverFetchError = ({ fetchError }) => { let body; if (fetchError.lang === 'painless') { - const managementUrl = chrome.navLinks.get('kibana:management').url; + const { chrome } = getServices(); + const mangagementUrlObj = chrome.navLinks.get('kibana:management'); + const managementUrl = mangagementUrlObj ? mangagementUrlObj.url : ''; const url = `${managementUrl}/kibana/index_patterns`; body = ( @@ -80,8 +90,8 @@ const DiscoverFetchError = ({ fetchError }) => { ); }; -const app = uiModules.get('apps/discover', ['react']); +export function createFetchErrorDirective(reactDirective: any) { + return reactDirective(wrapInI18nContext(DiscoverFetchError)); +} -app.directive('discoverFetchError', reactDirective => - reactDirective(wrapInI18nContext(DiscoverFetchError)) -); +getAngularModule().directive('discoverFetchError', createFetchErrorDirective); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js index cfcb654077152..0c633e23c5e4b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js @@ -20,16 +20,15 @@ import $ from 'jquery'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; import html from './discover_field.html'; import 'ui/directives/css_truncate'; import 'ui/directives/field_name'; import './string_progress_bar'; import detailsHtml from './lib/detail_views/string.html'; -const { uiModules, capabilities } = getServices(); -const app = uiModules.get('apps/discover'); -app.directive('discoverField', function ($compile) { + +export function createDiscoverFieldDirective($compile) { return { restrict: 'E', template: html, @@ -78,7 +77,7 @@ app.directive('discoverField', function ($compile) { }; - $scope.canVisualize = capabilities.visualize.show; + $scope.canVisualize = getServices().capabilities.visualize.show; $scope.toggleDisplay = function (field) { if (field.display) { @@ -135,4 +134,5 @@ app.directive('discoverField', function ($compile) { init(); } }; -}); +} + diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts index b78f993e18772..69865ec424325 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts @@ -16,18 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { getServices } from '../../kibana_services'; +import { wrapInI18nContext } from '../../kibana_services'; import { DiscoverFieldSearch } from './discover_field_search'; -const { wrapInI18nContext, uiModules } = getServices(); - -const app = uiModules.get('apps/discover'); - -app.directive('discoverFieldSearch', function(reactDirective: any) { +export function createFieldSearchDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(DiscoverFieldSearch), [ ['onChange', { watchDepth: 'reference' }], ['value', { watchDepth: 'value' }], ['types', { watchDepth: 'value' }], ]); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts index 5e3f678e388ad..46c8fa854847a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts @@ -16,18 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { getServices } from '../../kibana_services'; +import { wrapInI18nContext } from '../../kibana_services'; import { DiscoverIndexPattern } from './discover_index_pattern'; -const { wrapInI18nContext, uiModules } = getServices(); - -const app = uiModules.get('apps/discover'); - -app.directive('discoverIndexPatternSelect', function(reactDirective: any) { +export function createIndexPatternSelectDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(DiscoverIndexPattern), [ ['indexPatternList', { watchDepth: 'reference' }], ['selectedIndexPattern', { watchDepth: 'reference' }], ['setIndexPattern', { watchDepth: 'reference' }], ]); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js index 99a63efc0e0fc..cc3d864fd371e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js @@ -16,25 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -//field_name directive will be replaced very soon -import 'ui/directives/field_name'; -import './discover_field'; -import './discover_field_search_directive'; -import './discover_index_pattern_directive'; import _ from 'lodash'; import $ from 'jquery'; import rison from 'rison-node'; import { fieldCalculator } from './lib/field_calculator'; -import { - getServices, - FieldList -} from '../../kibana_services'; +import './discover_field'; +import './discover_field_search_directive'; +import './discover_index_pattern_directive'; +import { FieldList } from '../../kibana_services'; import fieldChooserTemplate from './field_chooser.html'; -const { uiModules } = getServices(); -const app = uiModules.get('apps/discover'); - -app.directive('discFieldChooser', function ($location, config, $route) { +export function createFieldChooserDirective($location, config, $route) { return { restrict: 'E', scope: { @@ -50,12 +42,11 @@ app.directive('discFieldChooser', function ($location, config, $route) { }, template: fieldChooserTemplate, link: function ($scope) { - $scope.showFilter = false; - $scope.toggleShowFilter = () => $scope.showFilter = !$scope.showFilter; + $scope.toggleShowFilter = () => ($scope.showFilter = !$scope.showFilter); $scope.selectedIndexPattern = $scope.indexPatternList.find( - (pattern) => pattern.id === $scope.indexPattern.id + pattern => pattern.id === $scope.indexPattern.id ); $scope.indexPatternList = _.sortBy($scope.indexPatternList, o => o.get('title')); $scope.setIndexPattern = function (id) { @@ -68,23 +59,17 @@ app.directive('discFieldChooser', function ($location, config, $route) { $route.reload(); }); - const filter = $scope.filter = { - props: [ - 'type', - 'aggregatable', - 'searchable', - 'missing', - 'name' - ], + const filter = ($scope.filter = { + props: ['type', 'aggregatable', 'searchable', 'missing', 'name'], defaults: { missing: true, type: 'any', - name: '' + name: '', }, boolOpts: [ { label: 'any', value: undefined }, { label: 'yes', value: true }, - { label: 'no', value: false } + { label: 'no', value: false }, ], reset: function () { filter.vals = _.clone(filter.defaults); @@ -105,15 +90,18 @@ app.directive('discFieldChooser', function ($location, config, $route) { return _.some(filter.props, function (prop) { return filter.vals[prop] !== filter.defaults[prop]; }); - } - }; + }, + }); function isFieldFiltered(field) { - const matchFilter = (filter.vals.type === 'any' || field.type === filter.vals.type); - const isAggregatable = (filter.vals.aggregatable == null || field.aggregatable === filter.vals.aggregatable); - const isSearchable = (filter.vals.searchable == null || field.searchable === filter.vals.searchable); - const scriptedOrMissing = !filter.vals.missing || field.type === '_source' || field.scripted || field.rowCount > 0; - const matchName = (!filter.vals.name || field.name.indexOf(filter.vals.name) !== -1); + const matchFilter = filter.vals.type === 'any' || field.type === filter.vals.type; + const isAggregatable = + filter.vals.aggregatable == null || field.aggregatable === filter.vals.aggregatable; + const isSearchable = + filter.vals.searchable == null || field.searchable === filter.vals.searchable; + const scriptedOrMissing = + !filter.vals.missing || field.type === '_source' || field.scripted || field.rowCount > 0; + const matchName = !filter.vals.name || field.name.indexOf(filter.vals.name) !== -1; return matchFilter && isAggregatable && isSearchable && scriptedOrMissing && matchName; } @@ -131,7 +119,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { filter.active = filter.getActive(); if (filter.vals) { let count = 0; - Object.keys(filter.vals).forEach((key) => { + Object.keys(filter.vals).forEach(key => { if (key === 'missing' || key === 'name') { return; } @@ -144,11 +132,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { } }); - $scope.$watchMulti([ - '[]fieldCounts', - '[]columns', - '[]hits' - ], function (cur, prev) { + $scope.$watchMulti(['[]fieldCounts', '[]columns', '[]hits'], function (cur, prev) { const newHits = cur[2] !== prev[2]; let fields = $scope.fields; const columns = $scope.columns || []; @@ -198,7 +182,9 @@ app.directive('discFieldChooser', function ($location, config, $route) { }; function getVisualizeUrl(field) { - if (!$scope.state) {return '';} + if (!$scope.state) { + return ''; + } let agg = {}; const isGeoPoint = field.type === 'geo_point'; @@ -211,18 +197,17 @@ app.directive('discFieldChooser', function ($location, config, $route) { schema: 'segment', params: { field: field.name, - interval: 'auto' - } + interval: 'auto', + }, }; - } else if (isGeoPoint) { agg = { type: 'geohash_grid', schema: 'segment', params: { field: field.name, - precision: 3 - } + precision: 3, + }, }; } else { agg = { @@ -231,26 +216,28 @@ app.directive('discFieldChooser', function ($location, config, $route) { params: { field: field.name, size: parseInt(config.get('discover:aggs:terms:size'), 10), - orderBy: '2' - } + orderBy: '2', + }, }; } - return '#/visualize/create?' + $.param(_.assign(_.clone($location.search()), { - indexPattern: $scope.state.index, - type: type, - _a: rison.encode({ - filters: $scope.state.filters || [], - query: $scope.state.query || undefined, - vis: { + return ( + '#/visualize/create?' + + $.param( + _.assign(_.clone($location.search()), { + indexPattern: $scope.state.index, type: type, - aggs: [ - { schema: 'metric', type: 'count', 'id': '2' }, - agg, - ] - } - }) - })); + _a: rison.encode({ + filters: $scope.state.filters || [], + query: $scope.state.query || undefined, + vis: { + type: type, + aggs: [{ schema: 'metric', type: 'count', id: '2' }, agg], + }, + }), + }) + ) + ); } $scope.computeDetails = function (field, recompute) { @@ -261,7 +248,7 @@ app.directive('discFieldChooser', function ($location, config, $route) { hits: $scope.hits, field: field, count: 5, - grouped: false + grouped: false, }), }; _.each(field.details.buckets, function (bucket) { @@ -285,13 +272,14 @@ app.directive('discFieldChooser', function ($location, config, $route) { const fieldNamesInDocs = _.keys(fieldCounts); const fieldNamesInIndexPattern = _.map(indexPattern.fields, 'name'); - _.difference(fieldNamesInDocs, fieldNamesInIndexPattern) - .forEach(function (unknownFieldName) { - fieldSpecs.push({ - name: unknownFieldName, - type: 'unknown' - }); + _.difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach(function ( + unknownFieldName + ) { + fieldSpecs.push({ + name: unknownFieldName, + type: 'unknown', }); + }); const fields = new FieldList(indexPattern, fieldSpecs); @@ -303,6 +291,6 @@ app.directive('discFieldChooser', function ($location, config, $route) { return fields; } - } + }, }; -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx similarity index 72% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js rename to src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx index ca3a47cad5075..7e4fc79839a52 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx @@ -17,19 +17,15 @@ * under the License. */ import React from 'react'; -import { getServices } from '../../kibana_services'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiText, - EuiToolTip, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui'; +import { wrapInI18nContext } from '../../kibana_services'; -const { wrapInI18nContext, uiModules } = getServices(); -const module = uiModules.get('discover/field_chooser'); +interface Props { + percent: number; + count: number; +} -function StringFieldProgressBar(props) { +function StringFieldProgressBar(props: Props) { return ( - + - - {props.percent}% - + {props.percent}% ); } -module.directive('stringFieldProgressBar', function (reactDirective) { +export function createStringFieldProgressBarDirective(reactDirective: any) { return reactDirective(wrapInI18nContext(StringFieldProgressBar)); -}); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js index 3531088e3847c..ea5c0ef39604d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js @@ -20,7 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { return { getServices: () => ({ SavedObjectFinder: jest.fn() diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/README.md b/src/legacy/core_plugins/kibana/public/discover/context/README.md deleted file mode 100644 index 18ba118b4da79..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/context/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# DISCOVER CONTEXT - -Placeholder for Discover's context functionality, that's currently in [../angular/context](../angular/context). -Once fully de-angularized it should be moved to this location \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index beeb6a7338f9d..3138008f3e3a0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -20,4 +20,3 @@ export * from './types'; export * from './search_embeddable_factory'; export * from './search_embeddable'; -export { SEARCH_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 9fee0cfc3ea00..273c7d80f216c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -21,7 +21,6 @@ import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { npStart } from 'ui/new_platform'; import { SearchSourceContract } from '../../../../../ui/public/courier'; import { esFilters, @@ -55,8 +54,6 @@ import { } from '../kibana_services'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; -const { data } = npStart.plugins; - interface SearchScope extends ng.IScope { columns?: string[]; description?: string; @@ -81,7 +78,7 @@ interface SearchEmbeddableConfig { editUrl: string; indexPatterns?: IndexPattern[]; editable: boolean; - queryFilter: unknown; + filterManager: FilterManager; } export class SearchEmbeddable extends Embeddable @@ -112,7 +109,7 @@ export class SearchEmbeddable extends Embeddable editUrl, indexPatterns, editable, - queryFilter, + filterManager, }: SearchEmbeddableConfig, initialInput: SearchInput, private readonly executeTriggerActions: TExecuteTriggerActions, @@ -124,7 +121,7 @@ export class SearchEmbeddable extends Embeddable parent ); - this.filterManager = queryFilter as FilterManager; + this.filterManager = filterManager; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; @@ -132,9 +129,10 @@ export class SearchEmbeddable extends Embeddable requests: new RequestAdapter(), }; this.initializeSearchScope(); - const { timefilter } = data.query.timefilter; - this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.fetch); + this.autoRefreshFetchSubscription = getServices() + .timefilter.getAutoRefreshFetch$() + .subscribe(this.fetch); this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { this.panelTitle = this.output.title || ''; @@ -281,10 +279,9 @@ export class SearchEmbeddable extends Embeddable }); const inspectorRequest = this.inspectorAdaptors.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then((body: any) => { + searchSource.getSearchRequestBody().then((body: Record) => { inspectorRequest.json(body); }); - this.searchScope.isLoading = true; try { @@ -292,7 +289,6 @@ export class SearchEmbeddable extends Embeddable const resp = await searchSource.fetch({ abortSignal: this.abortController.signal, }); - this.searchScope.isLoading = false; // Log response to inspector diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index ebea646a09889..b5475b2629c70 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -16,18 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { IPrivate } from 'ui/private'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import '../angular/doc_table'; +import { IInjector } from 'ui/chrome'; import { getServices } from '../kibana_services'; import { EmbeddableFactory, ErrorEmbeddable, Container, } from '../../../../../../plugins/embeddable/public'; + import { TimeRange } from '../../../../../../plugins/data/public'; -import { SavedSearchLoader } from '../types'; import { SearchEmbeddable } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; @@ -38,8 +37,15 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< SearchEmbeddable > { public readonly type = SEARCH_EMBEDDABLE_TYPE; + private $injector: IInjector | null; + private getInjector: () => Promise | null; + public isEditable: () => boolean; - constructor(private readonly executeTriggerActions: TExecuteTriggerActions) { + constructor( + private readonly executeTriggerActions: TExecuteTriggerActions, + getInjector: () => Promise, + isEditable: () => boolean + ) { super({ savedObjectMetaData: { name: i18n.translate('kbn.discover.savedSearch.savedObjectName', { @@ -49,10 +55,9 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< getIconForSavedObject: () => 'search', }, }); - } - - public isEditable() { - return getServices().capabilities.discover.save as boolean; + this.$injector = null; + this.getInjector = getInjector; + this.isEditable = isEditable; } public canCreateNew() { @@ -70,20 +75,19 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string; timeRange: TimeRange }, parent?: Container ): Promise { - const $injector = await getServices().getInjector(); + if (!this.$injector) { + this.$injector = await this.getInjector(); + } + const $injector = this.$injector as IInjector; const $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); - const searchLoader = $injector.get('savedSearches'); - const editUrl = await getServices().addBasePath( - `/app/kibana${searchLoader.urlFor(savedObjectId)}` - ); - - const Private = $injector.get('Private'); + const filterManager = getServices().filterManager; - const queryFilter = Private(getServices().FilterBarQueryFilterProvider); + const url = await getServices().getSavedSearchUrlById(savedObjectId); + const editUrl = getServices().addBasePath(`/app/kibana${url}`); try { - const savedObject = await searchLoader.get(savedObjectId); + const savedObject = await getServices().getSavedSearchById(savedObjectId); const indexPattern = savedObject.searchSource.getField('index'); return new SearchEmbeddable( { @@ -91,7 +95,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< $rootScope, $compile, editUrl, - queryFilter, + filterManager, editable: getServices().capabilities.discover.save as boolean, indexPatterns: indexPattern ? [indexPattern] : [], }, @@ -109,6 +113,3 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< return new ErrorEmbeddable('Saved searches can only be created from a saved object', input); } } - -const factory = new SearchEmbeddableFactory(getServices().uiActions.executeTriggerActions); -getServices().embeddable.registerEmbeddableFactory(factory.type, factory); diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts new file mode 100644 index 0000000000000..8e2d15ae48a1f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -0,0 +1,328 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import 'ui/angular-bootstrap'; +import { IPrivate } from 'ui/private'; +import { EuiIcon } from '@elastic/eui'; +// @ts-ignore +import { EventsProvider } from 'ui/events'; +import { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +import { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +import { createEsService } from 'ui/es'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +// @ts-ignore +import { PrivateProvider } from 'ui/private/private'; +import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; +// @ts-ignore +import { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; +// @ts-ignore +import { registerListenEventListener } from 'ui/directives/listen/listen'; +// @ts-ignore +import { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +// @ts-ignore +import { FieldNameDirectiveProvider } from 'ui/directives/field_name'; +// @ts-ignore +import { CollapsibleSidebarProvider } from 'ui/collapsible_sidebar/collapsible_sidebar'; +// @ts-ignore +import { CssTruncateProvide } from 'ui/directives/css_truncate'; +// @ts-ignore +import { FixedScrollProvider } from 'ui/fixed_scroll'; +// @ts-ignore +import { DebounceProviderTimeout } from 'ui/directives/debounce/debounce'; +// @ts-ignore +import { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +import { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +import { createRenderCompleteDirective } from 'ui/render_complete/directive'; +// @ts-ignore +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +import { configureAppAngularModule } from 'ui/legacy_compat'; +import { IndexPatterns } from '../../../../../plugins/data/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { NavigationStart } from '../../../navigation/public'; +import { createDocTableDirective } from './angular/doc_table/doc_table'; +import { createTableHeaderDirective } from './angular/doc_table/components/table_header'; +import { + createToolBarPagerButtonsDirective, + createToolBarPagerTextDirective, +} from './angular/doc_table/components/pager'; +import { createTableRowDirective } from './angular/doc_table/components/table_row'; +import { createPagerFactory } from './angular/doc_table/lib/pager/pager_factory'; +import { createInfiniteScrollDirective } from './angular/doc_table/infinite_scroll'; +import { createDocViewerDirective } from './angular/doc_viewer'; +import { createFieldSearchDirective } from './components/field_chooser/discover_field_search_directive'; +import { createIndexPatternSelectDirective } from './components/field_chooser/discover_index_pattern_directive'; +import { createStringFieldProgressBarDirective } from './components/field_chooser/string_progress_bar'; +// @ts-ignore +import { createFieldChooserDirective } from './components/field_chooser/field_chooser'; + +// @ts-ignore +import { createDiscoverFieldDirective } from './components/field_chooser/discover_field'; +import { DiscoverStartPlugins } from './plugin'; + +/** + * returns the main inner angular module, it contains all the parts of Angular Discover + * needs to render, so in the end the current 'kibana' angular module is no longer necessary + */ +export function getInnerAngularModule(name: string, core: CoreStart, deps: DiscoverStartPlugins) { + const module = initializeInnerAngularModule(name, core, deps.navigation); + configureAppAngularModule(module, core as LegacyCoreStart, true); + return module; +} + +/** + * returns a slimmer inner angular module for embeddable rendering + */ +export function getInnerAngularModuleEmbeddable( + name: string, + core: CoreStart, + deps: DiscoverStartPlugins +) { + const module = initializeInnerAngularModule(name, core, deps.navigation, true); + configureAppAngularModule(module, core as LegacyCoreStart, true); + return module; +} + +let initialized = false; + +export function initializeInnerAngularModule( + name = 'app/discover', + core: CoreStart, + navigation: NavigationStart, + embeddable = false +) { + if (!initialized) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core.uiSettings); + createLocalKbnUrlModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalGlobalStateModule(); + createLocalAppStateModule(); + createLocalStorageModule(); + createElasticSearchModule(); + createIndexPatternsModule(); + createPagerFactoryModule(); + createDocTableModule(); + initialized = true; + } + + if (embeddable) { + return angular + .module(name, [ + 'ngSanitize', + 'react', + 'ui.bootstrap', + 'discoverI18n', + 'discoverPrivate', + 'discoverDocTable', + 'discoverPagerFactory', + 'discoverPersistedState', + ]) + .config(watchMultiDecorator) + .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('fieldName', FieldNameDirectiveProvider) + .directive('renderComplete', createRenderCompleteDirective) + .service('debounce', ['$timeout', DebounceProviderTimeout]); + } + + return angular + .module(name, [ + 'ngSanitize', + 'ngRoute', + 'react', + 'ui.bootstrap', + 'elasticsearch', + 'discoverConfig', + 'discoverI18n', + 'discoverPrivate', + 'discoverPersistedState', + 'discoverTopNav', + 'discoverGlobalState', + 'discoverAppState', + 'discoverLocalStorageProvider', + 'discoverIndexPatterns', + 'discoverEs', + 'discoverDocTable', + 'discoverPagerFactory', + ]) + .config(watchMultiDecorator) + .run(registerListenEventListener) + .directive('icon', reactDirective => reactDirective(EuiIcon)) + .directive('kbnAccessibleClick', KbnAccessibleClickProvider) + .directive('fieldName', FieldNameDirectiveProvider) + .directive('collapsibleSidebar', CollapsibleSidebarProvider) + .directive('cssTruncate', CssTruncateProvide) + .directive('fixedScroll', FixedScrollProvider) + .directive('renderComplete', createRenderCompleteDirective) + .directive('discoverFieldSearch', createFieldSearchDirective) + .directive('discoverIndexPatternSelect', createIndexPatternSelectDirective) + .directive('stringFieldProgressBar', createStringFieldProgressBarDirective) + .directive('discoverField', createDiscoverFieldDirective) + .directive('discFieldChooser', createFieldChooserDirective) + .service('debounce', ['$timeout', DebounceProviderTimeout]); +} + +export function createLocalGlobalStateModule() { + angular + .module('discoverGlobalState', [ + 'discoverPrivate', + 'discoverConfig', + 'discoverKbnUrl', + 'discoverPromise', + ]) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('discoverPersistedState', ['discoverPrivate', 'discoverPromise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('discoverKbnUrl', ['discoverPrivate', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(uiSettings: IUiSettingsClient) { + angular + .module('discoverConfig', ['discoverPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: (value: string) => { + return uiSettings ? uiSettings.get(value) : undefined; + }, + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('discoverPromise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('discoverPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('discoverTopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('discoverI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalAppStateModule() { + angular + .module('discoverAppState', [ + 'discoverGlobalState', + 'discoverPrivate', + 'discoverConfig', + 'discoverKbnUrl', + 'discoverPromise', + ]) + .service('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }); +} + +function createLocalStorageModule() { + angular + .module('discoverLocalStorageProvider', ['discoverPrivate']) + .service('localStorage', createLocalStorageService('localStorage')) + .service('sessionStorage', createLocalStorageService('sessionStorage')); +} + +const createLocalStorageService = function(type: string) { + return function($window: any) { + return new Storage($window[type]); + }; +}; + +function createElasticSearchModule() { + angular + .module('discoverEs', ['elasticsearch', 'discoverConfig']) + // Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy + .service('es', createEsService); +} + +function createIndexPatternsModule() { + angular.module('discoverIndexPatterns', []).service('indexPatterns', IndexPatterns); +} + +function createPagerFactoryModule() { + angular.module('discoverPagerFactory', []).factory('pagerFactory', createPagerFactory); +} + +function createDocTableModule() { + angular + .module('discoverDocTable', [ + 'discoverKbnUrl', + 'discoverConfig', + 'discoverAppState', + 'discoverPagerFactory', + 'react', + ]) + .directive('docTable', createDocTableDirective) + .directive('kbnTableHeader', createTableHeaderDirective) + .directive('toolBarPagerText', createToolBarPagerTextDirective) + .directive('toolBarPagerText', createToolBarPagerTextDirective) + .directive('kbnTableRow', createTableRowDirective) + .directive('toolBarPagerButtons', createToolBarPagerButtonsDirective) + .directive('kbnInfiniteScroll', createInfiniteScrollDirective) + .directive('docViewer', createDocViewerDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts rename to src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts new file mode 100644 index 0000000000000..51f1521504be8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { + Capabilities, + ChromeStart, + CoreStart, + DocLinksStart, + ToastsStart, + IUiSettingsClient, +} from 'kibana/public'; +import * as docViewsRegistry from 'ui/registry/doc_views'; +import chromeLegacy from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +import { FilterManager, TimefilterContract } from 'src/plugins/data/public'; +// @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore +import { createSavedSearchesService } from '../saved_searches/saved_searches'; +// @ts-ignore +import { createSavedSearchFactory } from '../saved_searches/_saved_search'; +import { DiscoverStartPlugins } from '../plugin'; +import { start as legacyData } from '../../../../data/public/legacy'; +import { DataStart, IndexPatterns } from '../../../../data/public'; +import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; +import { SavedSearch } from '../types'; +import { SharePluginStart } from '../../../../../../plugins/share/public'; + +export interface DiscoverServices { + addBasePath: (path: string) => string; + capabilities: Capabilities; + chrome: ChromeStart; + core: CoreStart; + data: DataStart; + docLinks: DocLinksStart; + docViewsRegistry: docViewsRegistry.DocViewsRegistry; + eui_utils: EuiUtilsStart; + filterManager: FilterManager; + indexPatterns: IndexPatterns; + inspector: unknown; + metadata: { branch: string }; + share: SharePluginStart; + timefilter: TimefilterContract; + toastNotifications: ToastsStart; + // legacy + getSavedSearchById: (id: string) => Promise; + getSavedSearchUrlById: (id: string) => Promise; + State: unknown; + uiSettings: IUiSettingsClient; +} + +export async function buildGlobalAngularServices() { + const injector = await chromeLegacy.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + const kbnUrl = injector.get('kbnUrl'); + const State = Private(StateProvider); + const SavedSearchFactory = createSavedSearchFactory(Private); + const service = createSavedSearchesService(Private, SavedSearchFactory, kbnUrl, chromeLegacy); + return { + getSavedSearchById: async (id: string) => service.get(id), + getSavedSearchUrlById: async (id: string) => service.urlFor(id), + State, + }; +} + +export async function buildServices(core: CoreStart, plugins: DiscoverStartPlugins, test: false) { + const globalAngularServices = !test + ? await buildGlobalAngularServices() + : { + getSavedSearchById: async (id: string) => void id, + getSavedSearchUrlById: async (id: string) => void id, + State: null, + }; + + return { + ...globalAngularServices, + addBasePath: core.http.basePath.prepend, + capabilities: core.application.capabilities, + chrome: core.chrome, + core, + data: plugins.data, + docLinks: core.docLinks, + docViewsRegistry, + eui_utils: plugins.eui_utils, + filterManager: plugins.data.query.filterManager, + indexPatterns: legacyData.indexPatterns.indexPatterns, + inspector: plugins.inspector, + // @ts-ignore + metadata: core.injectedMetadata.getLegacyMetadata(), + share: plugins.share, + timefilter: plugins.data.query.timefilter.timefilter, + toastNotifications: core.notifications.toasts, + uiSettings: core.uiSettings, + }; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts new file mode 100644 index 0000000000000..bd62460fd6868 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IIndexPattern } from '../../../../../../plugins/data/common/index_patterns'; + +export function findIndexPatternById( + indexPatterns: IIndexPattern[], + id: string +): IIndexPattern | undefined { + if (!Array.isArray(indexPatterns) || !id) { + return; + } + return indexPatterns.find(o => o.id === id); +} + +/** + * Checks if the given defaultIndex exists and returns + * the first available index pattern id if not + */ +export function getFallbackIndexPatternId( + indexPatterns: IIndexPattern[], + defaultIndex: string = '' +): string { + if (defaultIndex && findIndexPatternById(indexPatterns, defaultIndex)) { + return defaultIndex; + } + return !indexPatterns || !indexPatterns.length || !indexPatterns[0].id ? '' : indexPatterns[0].id; +} + +/** + * A given index pattern id is checked for existence and a fallback is provided if it doesn't exist + * The provided defaultIndex is usually configured in Advanced Settings, if it's also invalid + * the first entry of the given list of Indexpatterns is used + */ +export function getIndexPatternId( + id: string = '', + indexPatterns: IIndexPattern[], + defaultIndex: string = '' +): string { + if (!id || !findIndexPatternById(indexPatterns, id)) { + return getFallbackIndexPatternId(indexPatterns, defaultIndex); + } + return id; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 35e48598f07a8..7f8ca4e96c5ac 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -18,7 +18,9 @@ */ import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects'; import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; +import { start as navigation } from '../../../navigation/public/legacy'; // Core will be looking for this when loading our plugin in the new platform export const plugin: PluginInitializer = ( @@ -27,6 +29,13 @@ export const plugin: PluginInitializer = ( return new DiscoverPlugin(initializerContext); }; -const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file +export const pluginInstance = plugin({} as PluginInitializerContext); +(async () => { + pluginInstance.setup(npSetup.core, npSetup.plugins); + pluginInstance.start(npStart.core, { ...npStart.plugins, navigation }); +})(); + +SavedObjectRegistryProvider.register((savedSearches: any) => { + return savedSearches; +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 822bf69e52e0b..393399f7da345 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -16,71 +16,40 @@ * specific language governing permissions and limitations * under the License. */ -import 'ui/collapsible_sidebar'; -import 'ui/directives/listen'; -import 'ui/directives/storage'; -import 'ui/fixed_scroll'; -import 'ui/directives/css_truncate'; - -import { npStart } from 'ui/new_platform'; -import chromeLegacy from 'ui/chrome'; import angular from 'angular'; // just used in embeddables and discover controller -import uiRoutes from 'ui/routes'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import { StateProvider } from 'ui/state_management/state'; -// @ts-ignore -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -// @ts-ignore -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { docTitle } from 'ui/doc_title'; -// @ts-ignore -import * as docViewsRegistry from 'ui/registry/doc_views'; -import { SearchSource } from '../../../../ui/public/courier'; +import { DiscoverServices } from './helpers/build_services'; -const services = { - // new plattform - core: npStart.core, - addBasePath: npStart.core.http.basePath.prepend, - capabilities: npStart.core.application.capabilities, - chrome: npStart.core.chrome, - docLinks: npStart.core.docLinks, - eui_utils: npStart.plugins.eui_utils, - inspector: npStart.plugins.inspector, - metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - toastNotifications: npStart.core.notifications.toasts, - uiSettings: npStart.core.uiSettings, - uiActions: npStart.plugins.uiActions, - embeddable: npStart.plugins.embeddable, - npData: npStart.plugins.data, - share: npStart.plugins.share, - timefilter: npStart.plugins.data.query.timefilter.timefilter, - // legacy - docTitle, - docViewsRegistry, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectRegistryProvider, - SavedObjectProvider, - SearchSource, - StateProvider, - uiModules, - uiRoutes, - wrapInI18nContext, -}; -export function getServices() { +let angularModule: any = null; +let services: DiscoverServices | null = null; + +/** + * set bootstrapped inner angular module + */ +export function setAngularModule(module: any) { + angularModule = module; +} + +/** + * get boostrapped inner angular module + */ +export function getAngularModule() { + return angularModule; +} + +export function getServices(): DiscoverServices { + if (!services) { + throw new Error('Discover services are not yet available'); + } return services; } -// EXPORT legacy static dependencies +export function setServices(newServices: any) { + services = newServices; +} + +// EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; +export { wrapInI18nContext } from 'ui/i18n'; export { buildVislibDimensions } from '../../../visualizations/public'; // @ts-ignore export { callAfterBindingsWorkaround } from 'ui/compat'; diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index 7c2fb4f118915..6679e8a3b8826 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -16,12 +16,20 @@ * specific language governing permissions and limitations * under the License. */ - import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; +import angular from 'angular'; import { IUiActionsStart } from 'src/plugins/ui_actions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { registerFeature } from './helpers/register_feature'; import './kibana_services'; import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embeddable/public'; +import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; +import { setAngularModule, setServices } from './kibana_services'; +import { NavigationStart } from '../../../navigation/public'; +import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public'; +import { buildServices } from './helpers/build_services'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; /** * These are the interfaces with your public contracts. You should export these @@ -30,28 +38,108 @@ import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embed */ export type DiscoverSetup = void; export type DiscoverStart = void; -interface DiscoverSetupPlugins { +export interface DiscoverSetupPlugins { uiActions: IUiActionsStart; embeddable: IEmbeddableSetup; + kibana_legacy: KibanaLegacySetup; } -interface DiscoverStartPlugins { +export interface DiscoverStartPlugins { uiActions: IUiActionsStart; embeddable: IEmbeddableStart; + navigation: NavigationStart; + eui_utils: EuiUtilsStart; + data: DataPublicPluginStart; + share: SharePluginStart; + inspector: any; } +const innerAngularName = 'app/discover'; +const embeddableAngularName = 'app/discoverEmbeddable'; +/** + * Contains Discover, one of the oldest parts of Kibana + * There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular + * Discover provides embeddables, those contain a slimmer Angular + */ export class DiscoverPlugin implements Plugin { + private servicesInitialized: boolean = false; + private innerAngularInitialized: boolean = false; + /** + * why are those functions public? they are needed for some mocha tests + * can be removed once all is Jest + */ + public initializeInnerAngular?: () => void; + public initializeServices?: () => void; constructor(initializerContext: PluginInitializerContext) {} setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup { - registerFeature(); - require('./angular'); + plugins.kibana_legacy.registerLegacyApp({ + id: 'discover', + title: 'Discover', + order: -1004, + euiIconType: 'discoverApp', + mount: async (context, params) => { + if (!this.initializeServices) { + throw Error('Discover plugin method initializeServices is undefined'); + } + if (!this.initializeInnerAngular) { + throw Error('Discover plugin method initializeInnerAngular is undefined'); + } + await this.initializeServices(); + await this.initializeInnerAngular(); + const { renderApp } = await import('./application'); + return renderApp(innerAngularName, params.element); + }, + }); } start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { - // TODO enable this when possible, seems it broke a functional test: - // dashboard mode Dashboard View Mode Dashboard viewer can paginate on a saved search - // const factory = new SearchEmbeddableFactory(plugins.uiActions.executeTriggerActions); - // plugins.embeddable.registerEmbeddableFactory(factory.type, factory); + // we need to register the application service at setup, but to render it + // there are some start dependencies necessary, for this reason + // initializeInnerAngular + initializeServices are assigned at start and used + // when the application/embeddable is mounted + this.initializeInnerAngular = async () => { + if (this.innerAngularInitialized) { + return; + } + // this is used by application mount and tests + const module = getInnerAngularModule(innerAngularName, core, plugins); + setAngularModule(module); + this.innerAngularInitialized = true; + }; + + this.initializeServices = async (test = false) => { + if (this.servicesInitialized) { + return; + } + const services = await buildServices(core, plugins, test); + setServices(services); + this.servicesInitialized = true; + }; + + this.registerEmbeddable(core, plugins); + registerFeature(); } - stop() {} + /** + * register embeddable with a slimmer embeddable version of inner angular + */ + private async registerEmbeddable(core: CoreStart, plugins: DiscoverStartPlugins) { + const { SearchEmbeddableFactory } = await import('./embeddable'); + const getInjector = async () => { + if (!this.initializeServices) { + throw Error('Discover plugin registerEmbeddable: initializeServices is undefined'); + } + await this.initializeServices(); + getInnerAngularModuleEmbeddable(embeddableAngularName, core, plugins); + const mountpoint = document.createElement('div'); + return angular.bootstrap(mountpoint, [embeddableAngularName]); + }; + const isEditable = () => core.application.capabilities.discover.save as boolean; + + const factory = new SearchEmbeddableFactory( + plugins.uiActions.executeTriggerActions, + getInjector, + isEditable + ); + plugins.embeddable.registerEmbeddableFactory(factory.type, factory); + } } diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js index 9bbc5baf4fc22..db2b2b5b22af7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js @@ -18,13 +18,13 @@ */ import { createLegacyClass } from 'ui/utils/legacy_class'; -import { getServices } from '../kibana_services'; +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -const { uiModules, SavedObjectProvider } = getServices(); +import { uiModules } from 'ui/modules'; const module = uiModules.get('discover/saved_searches', []); -module.factory('SavedSearch', function (Private) { +export function createSavedSearchFactory(Private) { const SavedObject = Private(SavedObjectProvider); createLegacyClass(SavedSearch).inherits(SavedObject); function SavedSearch(id) { @@ -32,7 +32,6 @@ module.factory('SavedSearch', function (Private) { type: SavedSearch.type, mapping: SavedSearch.mapping, searchSource: SavedSearch.searchSource, - id: id, defaults: { title: '', @@ -68,4 +67,6 @@ module.factory('SavedSearch', function (Private) { }; return SavedSearch; -}); +} + +module.factory('SavedSearch', createSavedSearchFactory); diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js index d68b7f0e0d097..7ebcba903cc7f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.js @@ -18,32 +18,33 @@ */ import './_saved_search'; -import 'ui/notify'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -const module = uiModules.get('discover/saved_searches'); + // Register this service with the saved object registry so it can be // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedSearches', - title: 'searches' + title: 'searches', }); -module.service('savedSearches', function (Private, SavedSearch, kbnUrl, chrome) { +export function createSavedSearchesService(Private, SavedSearch, kbnUrl, chrome) { const savedObjectClient = Private(SavedObjectsClientProvider); const savedSearchLoader = new SavedObjectLoader(SavedSearch, kbnUrl, chrome, savedObjectClient); // Customize loader properties since adding an 's' on type doesn't work for type 'search' . savedSearchLoader.loaderProperties = { name: 'searches', noun: 'Saved Search', - nouns: 'saved searches' + nouns: 'saved searches', }; - savedSearchLoader.urlFor = function (id) { + savedSearchLoader.urlFor = (id) => { return kbnUrl.eval('#/discover/{{id}}', { id: id }); }; return savedSearchLoader; -}); +} +const module = uiModules.get('discover/saved_searches'); +module.service('savedSearches', createSavedSearchesService); diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 98def2252b75c..cc438d338c7d5 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -23,6 +23,7 @@ import chrome from 'ui/chrome'; import routes from 'ui/routes'; import { uiModules } from 'ui/modules'; +import { npSetup } from 'ui/new_platform'; // import the uiExports that we want to "use" import 'uiExports/home'; @@ -59,11 +60,13 @@ import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; import { localApplicationService } from './local_application_service'; + +npSetup.plugins.kibana_legacy.forwardApp('doc', 'discover', { keepPrefix: true }); +npSetup.plugins.kibana_legacy.forwardApp('context', 'discover', { keepPrefix: true }); localApplicationService.attachToAngular(routes); routes.enable(); - routes .otherwise({ redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}` diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js index c9ea1e12c58fd..5f0994abc11e4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.js @@ -17,9 +17,7 @@ * under the License. */ -import { setup as data } from '../../../../../../../data/public/legacy'; -const { FieldImpl: Field } = data.indexPatterns.__LEGACY; - +import { Field } from '../../../../../../../../../plugins/data/public'; import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors'; import { docTitle } from 'ui/doc_title'; import { KbnUrlProvider } from 'ui/url'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js b/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js deleted file mode 100644 index 34b662838880e..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { - KuiEmptyTablePrompt, - KuiEmptyTablePromptPanel, - KuiButton, - KuiButtonIcon, -} from '@kbn/ui-framework/components'; - -function NoVisualizationsPrompt({ onCreateVis }) { - return ( - - } - > - - - } - message={i18n.translate('kbn.visualize.listing.noVisualizationsText', { - defaultMessage: `Looks like you don't have any visualizations. Let's create some!`, - })} - /> - - ); -} - -export { NoVisualizationsPrompt }; diff --git a/src/legacy/ui/public/accessibility/kbn_accessible_click.js b/src/legacy/ui/public/accessibility/kbn_accessible_click.js index 1abf322daa9a8..0b5016d882c41 100644 --- a/src/legacy/ui/public/accessibility/kbn_accessible_click.js +++ b/src/legacy/ui/public/accessibility/kbn_accessible_click.js @@ -43,52 +43,54 @@ import { } from '@elastic/eui'; import { uiModules } from '../modules'; -uiModules.get('kibana') - .directive('kbnAccessibleClick', function () { - return { - restrict: 'A', - controller: $element => { - $element.on('keydown', e => { +export function KbnAccessibleClickProvider() { + return { + restrict: 'A', + controller: $element => { + $element.on('keydown', e => { // Prevent a scroll from occurring if the user has hit space. - if (e.keyCode === keyCodes.SPACE) { - e.preventDefault(); - } - }); - }, - link: (scope, element, attrs) => { + if (e.keyCode === keyCodes.SPACE) { + e.preventDefault(); + } + }); + }, + link: (scope, element, attrs) => { // The whole point of this directive is to hack in functionality that native buttons provide // by default. - const elementType = element.prop('tagName'); + const elementType = element.prop('tagName'); - if (elementType === 'BUTTON') { - throw new Error(`kbnAccessibleClick doesn't need to be used on a button.`); - } + if (elementType === 'BUTTON') { + throw new Error(`kbnAccessibleClick doesn't need to be used on a button.`); + } - if (elementType === 'A' && attrs.href !== undefined) { - throw new Error(`kbnAccessibleClick doesn't need to be used on a link if it has a href attribute.`); - } + if (elementType === 'A' && attrs.href !== undefined) { + throw new Error(`kbnAccessibleClick doesn't need to be used on a link if it has a href attribute.`); + } - // We're emulating a click action, so we should already have a regular click handler defined. - if (!attrs.ngClick) { - throw new Error('kbnAccessibleClick requires ng-click to be defined on its element.'); - } + // We're emulating a click action, so we should already have a regular click handler defined. + if (!attrs.ngClick) { + throw new Error('kbnAccessibleClick requires ng-click to be defined on its element.'); + } - // If the developer hasn't already specified attributes required for accessibility, add them. - if (attrs.tabindex === undefined) { - element.attr('tabindex', '0'); - } + // If the developer hasn't already specified attributes required for accessibility, add them. + if (attrs.tabindex === undefined) { + element.attr('tabindex', '0'); + } - if (attrs.role === undefined) { - element.attr('role', 'button'); - } + if (attrs.role === undefined) { + element.attr('role', 'button'); + } - element.on('keyup', e => { + element.on('keyup', e => { // Support keyboard accessibility by emulating mouse click on ENTER or SPACE keypress. - if (accessibleClickKeys[e.keyCode]) { + if (accessibleClickKeys[e.keyCode]) { // Delegate to the click handler on the element (assumed to be ng-click). - element.click(); - } - }); - }, - }; - }); + element.click(); + } + }); + }, + }; +} + +uiModules.get('kibana') + .directive('kbnAccessibleClick', KbnAccessibleClickProvider); diff --git a/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js b/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js index 1a70e5b820003..3de138b7cd93d 100644 --- a/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js +++ b/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js @@ -21,65 +21,66 @@ import _ from 'lodash'; import $ from 'jquery'; import { uiModules } from '../modules'; - -uiModules - .get('kibana') - .directive('collapsibleSidebar', function () { +export function CollapsibleSidebarProvider() { // simply a list of all of all of angulars .col-md-* classes except 12 - const listOfWidthClasses = _.times(11, function (i) { return 'col-md-' + i; }); + const listOfWidthClasses = _.times(11, function (i) { + return 'col-md-' + i; + }); - return { - restrict: 'C', - link: function ($scope, $elem) { - let isCollapsed = false; - const $collapser = $( - `` - ); - // If the collapsable element has an id, also set aria-controls - if ($elem.attr('id')) { - $collapser.attr('aria-controls', $elem.attr('id')); - } - const $icon = $(''); - $collapser.append($icon); - const $siblings = $elem.siblings(); + ); + // If the collapsable element has an id, also set aria-controls + if ($elem.attr('id')) { + $collapser.attr('aria-controls', $elem.attr('id')); + } + const $icon = $(''); + $collapser.append($icon); + const $siblings = $elem.siblings(); - const siblingsClass = listOfWidthClasses.reduce(function (prev, className) { - if (prev) return prev; - return $siblings.hasClass(className) && className; - }, false); + const siblingsClass = listOfWidthClasses.reduce(function (prev, className) { + if (prev) return prev; + return $siblings.hasClass(className) && className; + }, false); - // If there is are only two elements we can assume the other one will take 100% of the width. - const hasSingleSibling = $siblings.length === 1 && siblingsClass; + // If there is are only two elements we can assume the other one will take 100% of the width. + const hasSingleSibling = $siblings.length === 1 && siblingsClass; - $collapser.on('click', function () { - if (isCollapsed) { - isCollapsed = false; - $elem.removeClass('closed'); - $icon.addClass('fa-chevron-circle-left'); - $icon.removeClass('fa-chevron-circle-right'); - $collapser.attr('aria-expanded', 'true'); - } else { - isCollapsed = true; - $elem.addClass('closed'); - $icon.removeClass('fa-chevron-circle-left'); - $icon.addClass('fa-chevron-circle-right'); - $collapser.attr('aria-expanded', 'false'); - } + $collapser.on('click', function () { + if (isCollapsed) { + isCollapsed = false; + $elem.removeClass('closed'); + $icon.addClass('fa-chevron-circle-left'); + $icon.removeClass('fa-chevron-circle-right'); + $collapser.attr('aria-expanded', 'true'); + } else { + isCollapsed = true; + $elem.addClass('closed'); + $icon.removeClass('fa-chevron-circle-left'); + $icon.addClass('fa-chevron-circle-right'); + $collapser.attr('aria-expanded', 'false'); + } - if (hasSingleSibling) { - $siblings.toggleClass(siblingsClass + ' col-md-12'); - } + if (hasSingleSibling) { + $siblings.toggleClass(siblingsClass + ' col-md-12'); + } - if ($scope.toggleSidebar) $scope.toggleSidebar(); - }); + if ($scope.toggleSidebar) $scope.toggleSidebar(); + }); - $collapser.appendTo($elem); - } - }; - }); + $collapser.appendTo($elem); + }, + }; +} + +uiModules.get('kibana').directive('collapsibleSidebar', CollapsibleSidebarProvider); diff --git a/src/legacy/ui/public/directives/css_truncate.js b/src/legacy/ui/public/directives/css_truncate.js index 7105563e0ce5f..ac8e29258fddb 100644 --- a/src/legacy/ui/public/directives/css_truncate.js +++ b/src/legacy/ui/public/directives/css_truncate.js @@ -20,12 +20,11 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -module.directive('cssTruncate', function () { +export function CssTruncateProvide() { return { restrict: 'A', scope: {}, link: function ($scope, $elem, attrs) { - $elem.css({ overflow: 'hidden', 'white-space': 'nowrap', @@ -35,10 +34,12 @@ module.directive('cssTruncate', function () { if (attrs.cssTruncateExpandable != null) { $scope.$watch( - function () { return $elem.html(); }, + function () { + return $elem.html(); + }, function () { if ($elem[0].offsetWidth < $elem[0].scrollWidth) { - $elem.css({ 'cursor': 'pointer' }); + $elem.css({ cursor: 'pointer' }); $elem.bind('click', function () { $scope.toggle(); }); @@ -59,6 +60,8 @@ module.directive('cssTruncate', function () { $elem.unbind('click'); $elem.unbind('mouseenter'); }); - } + }, }; -}); +} + +module.directive('cssTruncate', CssTruncateProvide); diff --git a/src/legacy/ui/public/directives/debounce/debounce.js b/src/legacy/ui/public/directives/debounce/debounce.js index 3b384d7088743..06c82d4356a4a 100644 --- a/src/legacy/ui/public/directives/debounce/debounce.js +++ b/src/legacy/ui/public/directives/debounce/debounce.js @@ -24,7 +24,7 @@ import { uiModules } from '../../modules'; const module = uiModules.get('kibana'); -module.service('debounce', ['$timeout', function ($timeout) { +export function DebounceProviderTimeout($timeout) { return function (func, wait, options) { let timeout; let args; @@ -68,7 +68,9 @@ module.service('debounce', ['$timeout', function ($timeout) { return debounce; }; -}]); +} + +module.service('debounce', ['$timeout', DebounceProviderTimeout]); export function DebounceProvider(debounce) { return debounce; diff --git a/src/legacy/ui/public/directives/field_name.js b/src/legacy/ui/public/directives/field_name.js index b159b71238186..aff849fc5602f 100644 --- a/src/legacy/ui/public/directives/field_name.js +++ b/src/legacy/ui/public/directives/field_name.js @@ -21,7 +21,7 @@ import { uiModules } from '../modules'; import { wrapInI18nContext } from 'ui/i18n'; const module = uiModules.get('kibana'); -module.directive('fieldName', function (config, reactDirective) { +export function FieldNameDirectiveProvider(config, reactDirective) { return reactDirective( wrapInI18nContext(FieldName), [ @@ -34,4 +34,6 @@ module.directive('fieldName', function (config, reactDirective) { useShortDots: config.get('shortDots:enable'), } ); -}); +} + +module.directive('fieldName', FieldNameDirectiveProvider); diff --git a/src/legacy/ui/public/directives/listen/listen.js b/src/legacy/ui/public/directives/listen/listen.js index b877e8ee6b08e..fddc85a4f4914 100644 --- a/src/legacy/ui/public/directives/listen/listen.js +++ b/src/legacy/ui/public/directives/listen/listen.js @@ -19,22 +19,22 @@ import { uiModules } from '../../modules'; -uiModules.get('kibana') - .run(function ($rootScope) { +export function registerListenEventListener($rootScope) { + /** + * Helper that registers an event listener, and removes that listener when + * the $scope is destroyed. + * + * @param {SimpleEmitter} emitter - the event emitter to listen to + * @param {string} eventName - the event name + * @param {Function} handler - the event handler + * @return {undefined} + */ + $rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) { + emitter.on(eventName, handler); + this.$on('$destroy', function () { + emitter.off(eventName, handler); + }); + }; +} - /** - * Helper that registers an event listener, and removes that listener when - * the $scope is destroyed. - * - * @param {SimpleEmitter} emitter - the event emitter to listen to - * @param {string} eventName - the event name - * @param {Function} handler - the event handler - * @return {undefined} - */ - $rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) { - emitter.on(eventName, handler); - this.$on('$destroy', function () { - emitter.off(eventName, handler); - }); - }; - }); +uiModules.get('kibana').run(registerListenEventListener); diff --git a/src/legacy/ui/public/directives/watch_multi/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/watch_multi.js index add95e8146f26..d1b43f74f56ab 100644 --- a/src/legacy/ui/public/directives/watch_multi/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/watch_multi.js @@ -21,10 +21,8 @@ import _ from 'lodash'; import { uiModules } from '../../modules'; import { callEach } from '../../utils/function'; -uiModules.get('kibana') - .config(function ($provide) { - - $provide.decorator('$rootScope', function ($delegate) { +export function watchMultiDecorator($provide) { + $provide.decorator('$rootScope', function ($delegate) { /** * Watch multiple expressions with a single callback. Along * with making code simpler it also merges all of the watcher @@ -53,23 +51,30 @@ uiModules.get('kibana') * @param {Function} fn - the callback function * @return {Function} - an unwatch function, just like the return value of $watch */ - $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { - if (!Array.isArray(expressions)) throw new TypeError('expected an array of expressions to watch'); - if (!_.isFunction(fn)) throw new TypeError('expected a function that is triggered on each watch'); - - const $scope = this; - const vals = new Array(expressions.length); - const prev = new Array(expressions.length); - let fire = false; - let init = 0; - const neededInits = expressions.length; - - // first, register all of the multi-watchers - const unwatchers = expressions.map(function (expr, i) { - expr = normalizeExpression($scope, expr); - if (!expr) return; - - return expr.fn.call($scope, expr.get, function (newVal, oldVal) { + $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { + if (!Array.isArray(expressions)) { + throw new TypeError('expected an array of expressions to watch'); + } + + if (!_.isFunction(fn)) { + throw new TypeError('expected a function that is triggered on each watch'); + } + const $scope = this; + const vals = new Array(expressions.length); + const prev = new Array(expressions.length); + let fire = false; + let init = 0; + const neededInits = expressions.length; + + // first, register all of the multi-watchers + const unwatchers = expressions.map(function (expr, i) { + expr = normalizeExpression($scope, expr); + if (!expr) return; + + return expr.fn.call( + $scope, + expr.get, + function (newVal, oldVal) { if (newVal === oldVal) { init += 1; } @@ -77,60 +82,69 @@ uiModules.get('kibana') vals[i] = newVal; prev[i] = oldVal; fire = true; - }, expr.deep); - }); - - // then, the watcher that checks to see if any of - // the other watchers triggered this cycle - let flip = false; - unwatchers.push($scope.$watch(function () { - if (init < neededInits) return init; - - if (fire) { - fire = false; - flip = !flip; + }, + expr.deep + ); + }); + + // then, the watcher that checks to see if any of + // the other watchers triggered this cycle + let flip = false; + unwatchers.push( + $scope.$watch( + function () { + if (init < neededInits) return init; + + if (fire) { + fire = false; + flip = !flip; + } + return flip; + }, + function () { + if (init < neededInits) return false; + + fn(vals.slice(0), prev.slice(0)); + vals.forEach(function (v, i) { + prev[i] = v; + }); } - return flip; - }, function () { - if (init < neededInits) return false; + ) + ); - fn(vals.slice(0), prev.slice(0)); - vals.forEach(function (v, i) { - prev[i] = v; - }); - })); + return _.partial(callEach, unwatchers); + }; - return _.partial(callEach, unwatchers); + function normalizeExpression($scope, expr) { + if (!expr) return; + const norm = { + fn: $scope.$watch, + deep: false, }; - function normalizeExpression($scope, expr) { - if (!expr) return; - const norm = { - fn: $scope.$watch, - deep: false - }; - - if (_.isFunction(expr)) return _.assign(norm, { get: expr }); - if (_.isObject(expr)) return _.assign(norm, expr); - if (!_.isString(expr)) return; - - if (expr.substr(0, 2) === '[]') { - return _.assign(norm, { - fn: $scope.$watchCollection, - get: expr.substr(2) - }); - } - - if (expr.charAt(0) === '=') { - return _.assign(norm, { - deep: true, - get: expr.substr(1) - }); - } - - return _.assign(norm, { get: expr }); + if (_.isFunction(expr)) return _.assign(norm, { get: expr }); + if (_.isObject(expr)) return _.assign(norm, expr); + if (!_.isString(expr)) return; + + if (expr.substr(0, 2) === '[]') { + return _.assign(norm, { + fn: $scope.$watchCollection, + get: expr.substr(2), + }); } - return $delegate; - }); + if (expr.charAt(0) === '=') { + return _.assign(norm, { + deep: true, + get: expr.substr(1), + }); + } + + return _.assign(norm, { get: expr }); + } + + return $delegate; }); +} + +uiModules.get('kibana').config(watchMultiDecorator); diff --git a/src/legacy/ui/public/es.js b/src/legacy/ui/public/es.js index d1204f8316f0d..601beba832c33 100644 --- a/src/legacy/ui/public/es.js +++ b/src/legacy/ui/public/es.js @@ -44,16 +44,17 @@ const plugins = [function (Client, config) { config.connectionClass = CustomAngularConnector; }]; +export function createEsService(esFactory, esUrl, esApiVersion, esRequestTimeout) { + return esFactory({ + host: esUrl, + log: 'info', + requestTimeout: esRequestTimeout, + apiVersion: esApiVersion, + plugins + }); +} + uiModules .get('kibana', ['elasticsearch', 'kibana/config']) - //Elasticsearch client used for requesting data. Connects to the /elasticsearch proxy - .service('es', function (esFactory, esUrl, esApiVersion, esRequestTimeout) { - return esFactory({ - host: esUrl, - log: 'info', - requestTimeout: esRequestTimeout, - apiVersion: esApiVersion, - plugins - }); - }); + .service('es', createEsService); diff --git a/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap b/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap index 2fe29dd29a59b..6bd9652d2a1b0 100644 --- a/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap +++ b/src/legacy/ui/public/exit_full_screen/__snapshots__/exit_full_screen_button.test.js.snap @@ -8,32 +8,28 @@ exports[`is rendered 1`] = ` > In full screen mode, press ESC to exit.

-
+
diff --git a/src/legacy/ui/public/fixed_scroll.js b/src/legacy/ui/public/fixed_scroll.js index fcdde7bbdeb32..64c0ae5850bc9 100644 --- a/src/legacy/ui/public/fixed_scroll.js +++ b/src/legacy/ui/public/fixed_scroll.js @@ -24,30 +24,22 @@ import { DebounceProvider } from 'ui/directives/debounce'; const SCROLLER_HEIGHT = 20; -/** - * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events - * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar - * might be waaaay down the page, like the doc table on Discover. - */ -uiModules - .get('kibana') - .directive('fixedScroll', function (Private) { - const debounce = Private(DebounceProvider); - - return { - restrict: 'A', - link: function ($scope, $el) { - let $window = $(window); - let $scroller = $('
').height(SCROLLER_HEIGHT); +export function FixedScrollProvider(Private) { + const debounce = Private(DebounceProvider); + return { + restrict: 'A', + link: function ($scope, $el) { + let $window = $(window); + let $scroller = $('
').height(SCROLLER_HEIGHT); - /** + /** * Remove the listeners bound in listen() * @type {function} */ - let unlisten = _.noop; + let unlisten = _.noop; - /** + /** * Listen for scroll events on the $scroller and the $el, sets unlisten() * * unlisten must be called before calling or listen() will throw an Error @@ -58,98 +50,109 @@ uiModules * @throws {Error} If unlisten was not called first * @return {undefined} */ - function listen() { - if (unlisten !== _.noop) { - throw new Error('fixedScroll listeners were not cleaned up properly before re-listening!'); - } + function listen() { + if (unlisten !== _.noop) { + throw new Error( + 'fixedScroll listeners were not cleaned up properly before re-listening!' + ); + } - let blockTo; - function bind($from, $to) { - function handler() { - if (blockTo === $to) return (blockTo = null); - $to.scrollLeft((blockTo = $from).scrollLeft()); - } - - $from.on('scroll', handler); - return function () { - $from.off('scroll', handler); - }; + let blockTo; + function bind($from, $to) { + function handler() { + if (blockTo === $to) return (blockTo = null); + $to.scrollLeft((blockTo = $from).scrollLeft()); } - unlisten = _.flow( - bind($el, $scroller), - bind($scroller, $el), - function () { unlisten = _.noop; } - ); + $from.on('scroll', handler); + return function () { + $from.off('scroll', handler); + }; } - /** + unlisten = _.flow( + bind($el, $scroller), + bind($scroller, $el), + function () { + unlisten = _.noop; + } + ); + } + + /** * Revert DOM changes and event listeners * @return {undefined} */ - function cleanUp() { - unlisten(); - $scroller.detach(); - $el.css('padding-bottom', 0); - } + function cleanUp() { + unlisten(); + $scroller.detach(); + $el.css('padding-bottom', 0); + } - /** + /** * Modify the DOM and attach event listeners based on need. * Is called many times to re-setup, must be idempotent * @return {undefined} */ - function setup() { - cleanUp(); + function setup() { + cleanUp(); - const containerWidth = $el.width(); - const contentWidth = $el.prop('scrollWidth'); - const containerHorizOverflow = contentWidth - containerWidth; + const containerWidth = $el.width(); + const contentWidth = $el.prop('scrollWidth'); + const containerHorizOverflow = contentWidth - containerWidth; - const elTop = $el.offset().top - $window.scrollTop(); - const elBottom = elTop + $el.height(); - const windowVertOverflow = elBottom - $window.height(); + const elTop = $el.offset().top - $window.scrollTop(); + const elBottom = elTop + $el.height(); + const windowVertOverflow = elBottom - $window.height(); - const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0; - if (!requireScroller) return; + const requireScroller = containerHorizOverflow > 0 && windowVertOverflow > 0; + if (!requireScroller) return; - // push the content away from the scroller - $el.css('padding-bottom', SCROLLER_HEIGHT); + // push the content away from the scroller + $el.css('padding-bottom', SCROLLER_HEIGHT); - // fill the scroller with a dummy element that mimics the content - $scroller - .width(containerWidth) - .html($('
').css({ width: contentWidth, height: SCROLLER_HEIGHT })) - .insertAfter($el); + // fill the scroller with a dummy element that mimics the content + $scroller + .width(containerWidth) + .html($('
').css({ width: contentWidth, height: SCROLLER_HEIGHT })) + .insertAfter($el); - // listen for scroll events - listen(); - } + // listen for scroll events + listen(); + } - let width; - let scrollWidth; - function checkWidth() { - const newScrollWidth = $el.prop('scrollWidth'); - const newWidth = $el.width(); + let width; + let scrollWidth; + function checkWidth() { + const newScrollWidth = $el.prop('scrollWidth'); + const newWidth = $el.width(); - if (scrollWidth !== newScrollWidth || width !== newWidth) { - $scope.$apply(setup); + if (scrollWidth !== newScrollWidth || width !== newWidth) { + $scope.$apply(setup); - scrollWidth = newScrollWidth; - width = newWidth; - } + scrollWidth = newScrollWidth; + width = newWidth; } - - const debouncedCheckWidth = debounce(checkWidth, 100, { - invokeApply: false, - }); - $scope.$watch(debouncedCheckWidth); - - // cleanup when the scope is destroyed - $scope.$on('$destroy', function () { - cleanUp(); - debouncedCheckWidth.cancel(); - $scroller = $window = null; - }); } - }; - }); + + const debouncedCheckWidth = debounce(checkWidth, 100, { + invokeApply: false, + }); + $scope.$watch(debouncedCheckWidth); + + // cleanup when the scope is destroyed + $scope.$on('$destroy', function () { + cleanUp(); + debouncedCheckWidth.cancel(); + $scroller = $window = null; + }); + }, + }; +} + +/** + * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events + * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar + * might be waaaay down the page, like the doc table on Discover. + */ +uiModules.get('kibana').directive('fixedScroll', FixedScrollProvider); diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 9ff09835e48da..6f3d39299f970 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -17,18 +17,18 @@ * under the License. */ -import { dataPluginMock } from '../../../../core_plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; +import { indexPatterns as npIndexPatterns } from '../../../../../plugins/data/public'; -const dataSetup = dataPluginMock.createSetup(); +const dataSetup = dataPluginMock.createSetupContract(); // mocks for stateful code -export const { FieldImpl } = dataSetup.indexPatterns!.__LEGACY; -export const { - FieldList, - flattenHitWrapper, - formatHitProvider, - indexPatterns, -} = dataSetup.indexPatterns!; +export const { indexPatterns } = dataSetup.indexPatterns!; + +export const flattenHitWrapper = npIndexPatterns.flattenHitWrapper; +export const formatHitProvider = npIndexPatterns.formatHitProvider; // static code export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; +export { FieldList } from '../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index 06001667c9e53..200276c1727fc 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -24,14 +24,6 @@ * from ui/index_patterns for backwards compatibility. */ -import { start as data } from '../../../core_plugins/data/public/legacy'; - -export const { - FieldList, // only used in Discover and StubIndexPattern - flattenHitWrapper, - formatHitProvider, -} = data.indexPatterns; - import { indexPatterns } from '../../../../plugins/data/public'; // static code @@ -42,6 +34,8 @@ export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CH export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; export const validateIndexPattern = indexPatterns.validate; +export const flattenHitWrapper = indexPatterns.flattenHitWrapper; +export const formatHitProvider = indexPatterns.formatHitProvider; // types export { @@ -52,3 +46,5 @@ export { IndexPatterns, StaticIndexPattern, } from '../../../core_plugins/data/public'; + +export { FieldList } from '../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/registry/doc_views.ts b/src/legacy/ui/public/registry/doc_views.ts index 097808c5dcfcc..bf1e8416ae66d 100644 --- a/src/legacy/ui/public/registry/doc_views.ts +++ b/src/legacy/ui/public/registry/doc_views.ts @@ -21,6 +21,12 @@ import { DocView, DocViewInput, ElasticSearchHit, DocViewInputFn } from './doc_v export { DocViewRenderProps, DocView, DocViewRenderFn } from './doc_views_types'; +export interface DocViewsRegistry { + docViews: DocView[]; + addDocView: (docView: DocViewInput) => void; + getDocViewsSorted: (hit: ElasticSearchHit) => DocView[]; +} + export const docViews: DocView[] = []; /** diff --git a/src/legacy/ui/public/registry/doc_views_helpers.tsx b/src/legacy/ui/public/registry/doc_views_helpers.tsx index 1ff00713b10ef..d9e42e71dfff1 100644 --- a/src/legacy/ui/public/registry/doc_views_helpers.tsx +++ b/src/legacy/ui/public/registry/doc_views_helpers.tsx @@ -26,7 +26,7 @@ import { AngularController, AngularDirective, } from './doc_views_types'; -import { DocViewerError } from '../../../core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error'; +import { DocViewerError } from '../../../core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error'; /** * Compiles and injects the give angular template into the given dom node diff --git a/src/legacy/ui/public/render_complete/directive.js b/src/legacy/ui/public/render_complete/directive.js index 6bde2293898b6..0e37ec964d3f0 100644 --- a/src/legacy/ui/public/render_complete/directive.js +++ b/src/legacy/ui/public/render_complete/directive.js @@ -20,13 +20,14 @@ import { uiModules } from '../modules'; import { RenderCompleteHelper } from '../../../../plugins/kibana_utils/public'; -uiModules - .get('kibana') - .directive('renderComplete', () => ({ +export function createRenderCompleteDirective() { + return { controller($scope, $element) { const el = $element[0]; const renderCompleteHelper = new RenderCompleteHelper(el); - $scope.$on('$destroy', renderCompleteHelper.destroy); } - })); + }; +} + +uiModules.get('kibana').directive('renderComplete', createRenderCompleteDirective); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx index 66acc9e247e63..839dc0024bbea 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { act } from '@testing-library/react-hooks'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiButtonGroup } from '@elastic/eui'; @@ -173,10 +172,8 @@ describe('VisLegend Component', () => { it('should call unHighlight handler when legend item is unhovered', () => { const first = getLegendItems(wrapper).first(); - act(() => { - first.simulate('mouseEnter'); - first.simulate('mouseLeave'); - }); + first.simulate('mouseEnter'); + first.simulate('mouseLeave'); expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); }); diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 5c1669b716eca..291d9feea3c40 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -55,7 +55,6 @@ export const UI_EXPORT_DEFAULTS = { ], embeddableFactories: [ 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', - 'plugins/kibana/discover/embeddable/search_embeddable_factory', ], search: [ 'ui/courier/search_strategy/default_search_strategy', diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 54977c7e66976..c9906fb136052 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -20,8 +20,7 @@ import { template, escape, keys } from 'lodash'; // @ts-ignore import { noWhiteSpace } from '../../../../../legacy/core_plugins/kibana/common/utils/no_white_space'; -// @ts-ignore -import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../utils'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, HtmlContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index 0edd219ca60f9..b2d92cf475a16 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -22,8 +22,7 @@ import { asPrettyString } from '../index'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; -// @ts-ignore -import { shortenDottedString } from '../../../../../legacy/core_plugins/kibana/common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../utils'; const TRANSFORM_OPTIONS = [ { diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index f9bbeb5f4b3f3..b334342a57ec6 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -22,5 +22,5 @@ export * from './field_formats'; export * from './kbn_field_types'; export * from './index_patterns'; export * from './es_query'; - +export * from './utils'; export * from './types'; diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js b/src/plugins/data/common/utils/index.ts similarity index 81% rename from src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js rename to src/plugins/data/common/utils/index.ts index 9554642c225fd..7196c96989e97 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js +++ b/src/plugins/data/common/utils/index.ts @@ -17,10 +17,4 @@ * under the License. */ -import { getServices } from '../kibana_services'; -import './saved_searches'; - - -getServices().SavedObjectRegistryProvider.register((savedSearches) => { - return savedSearches; -}); +export { shortenDottedString } from './shorten_dotted_string'; diff --git a/src/plugins/data/common/utils/shorten_dotted_string.test.ts b/src/plugins/data/common/utils/shorten_dotted_string.test.ts new file mode 100644 index 0000000000000..5f8d084ce5e88 --- /dev/null +++ b/src/plugins/data/common/utils/shorten_dotted_string.test.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { shortenDottedString } from './shorten_dotted_string'; + +describe('shortenDottedString', () => { + test('should convert a dot.notated.string into a short string', () => { + expect(shortenDottedString('dot.notated.string')).toBe('d.n.string'); + }); + + test('should ignore non-string values', () => { + const obj = { key: 'val' }; + + expect(shortenDottedString(true)).toBe(true); + expect(shortenDottedString(123)).toBe(123); + expect(shortenDottedString(obj)).toBe(obj); + }); +}); diff --git a/src/legacy/core_plugins/data/public/mocks.ts b/src/plugins/data/common/utils/shorten_dotted_string.ts similarity index 68% rename from src/legacy/core_plugins/data/public/mocks.ts rename to src/plugins/data/common/utils/shorten_dotted_string.ts index 39d1296ddf8bc..379413c0d91c8 100644 --- a/src/legacy/core_plugins/data/public/mocks.ts +++ b/src/plugins/data/common/utils/shorten_dotted_string.ts @@ -17,19 +17,14 @@ * under the License. */ -import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; +const DOT_PREFIX_RE = /(.).+?\./g; -function createDataSetupMock() { - return { - indexPatterns: indexPatternsServiceMock.createSetupContract(), - }; -} - -function createDataStartMock() { - return {}; +/** + * Convert a dot.notated.string into a short + * version (d.n.string) + * + * @return {any} + */ +export function shortenDottedString(input: any) { + return typeof input !== 'string' ? input : input.replace(DOT_PREFIX_RE, '$1.'); } - -export const dataPluginMock = { - createSetup: createDataSetupMock, - createStart: createDataStartMock, -}; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts similarity index 94% rename from src/legacy/core_plugins/data/public/index_patterns/fields/field.ts rename to src/plugins/data/public/index_patterns/fields/field.ts index 91964655f6f3e..ae17188de8625 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -20,26 +20,19 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; -// @ts-ignore -import { shortenDottedString } from '../../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; import { IndexPattern } from '../index_patterns'; import { getNotifications, getFieldFormats } from '../services'; - import { - FieldFormat, - getKbnFieldType, IFieldType, + getKbnFieldType, IFieldSubType, -} from '../../../../../../plugins/data/public'; + FieldFormat, + shortenDottedString, +} from '../../../common'; export type FieldSpec = Record; -/** @deprecated - * Please use IFieldType instead - * */ -export type FieldType = IFieldType; - -export class Field implements FieldType { +export class Field implements IFieldType { name: string; type: string; script?: string; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts similarity index 91% rename from src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts rename to src/plugins/data/public/index_patterns/fields/field_list.ts index 108aacc8e07de..ff6706cec6c34 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -19,7 +19,8 @@ import { findIndex } from 'lodash'; import { IndexPattern } from '../index_patterns'; -import { Field, FieldType, FieldSpec } from './field'; +import { IFieldType } from '../../../common'; +import { Field, FieldSpec } from './field'; type FieldMap = Map; @@ -27,7 +28,7 @@ export interface FieldListInterface extends Array { getByName(name: Field['name']): Field | undefined; getByType(type: Field['type']): Field[]; add(field: FieldSpec): void; - remove(field: FieldType): void; + remove(field: IFieldType): void; } export class FieldList extends Array implements FieldListInterface { @@ -42,7 +43,7 @@ export class FieldList extends Array implements FieldListInterface { } this.groups.get(field.type)!.set(field.name, field); }; - private removeByGroup = (field: FieldType) => this.groups.get(field.type)!.delete(field.name); + private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); constructor(indexPattern: IndexPattern, specs: FieldSpec[] = [], shortDotsEnable = false) { super(); @@ -61,7 +62,7 @@ export class FieldList extends Array implements FieldListInterface { this.setByGroup(newField); }; - remove = (field: FieldType) => { + remove = (field: IFieldType) => { this.removeByGroup(field); this.byName.delete(field.name); diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/index.ts b/src/plugins/data/public/index_patterns/fields/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/index.ts rename to src/plugins/data/public/index_patterns/fields/index.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.js b/src/plugins/data/public/index_patterns/fields/obj_define.js similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.js rename to src/plugins/data/public/index_patterns/fields/obj_define.js diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.test.js b/src/plugins/data/public/index_patterns/fields/obj_define.test.js similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/fields/obj_define.test.js rename to src/plugins/data/public/index_patterns/fields/obj_define.test.js diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index aedfc18db3ade..7492f9372e44a 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -25,6 +25,8 @@ import { ILLEGAL_CHARACTERS, validateIndexPattern, } from './lib'; +import { getRoutes, getFromSavedObject } from './utils'; +import { flattenHitWrapper, formatHitProvider } from './index_patterns'; export const indexPatterns = { ILLEGAL_CHARACTERS_KEY, @@ -33,4 +35,13 @@ export const indexPatterns = { ILLEGAL_CHARACTERS, IndexPatternMissingIndices, validate: validateIndexPattern, + getRoutes, + getFromSavedObject, + flattenHitWrapper, + formatHitProvider, }; + +export { IndexPatternsService } from './index_patterns_service'; +export { Field, FieldList, FieldListInterface } from './fields'; +export { IndexPattern, IndexPatterns } from './index_patterns'; +export { IndexPatternsStart, IndexPatternsSetup } from './types'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts b/src/plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts rename to src/plugins/data/public/index_patterns/index_patterns/_fields_fetcher.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts b/src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts similarity index 97% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts rename to src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts index a3653bb529fa3..eb6c69b414316 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/_pattern_cache.ts @@ -19,7 +19,7 @@ import { IndexPattern } from './index_pattern'; -export interface PatternCache { +interface PatternCache { get: (id: string) => IndexPattern; set: (id: string, value: IndexPattern) => IndexPattern; clear: (id: string) => void; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/flatten_hit.ts rename to src/plugins/data/public/index_patterns/index_patterns/flatten_hit.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts rename to src/plugins/data/public/index_patterns/index_patterns/format_hit.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index_patterns/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index.ts rename to src/plugins/data/public/index_patterns/index_patterns/index.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts similarity index 95% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index ee9f9b493ebf2..a0a884454a3f0 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -20,24 +20,23 @@ import { defaults, pluck, last, get } from 'lodash'; import { IndexPattern } from './index_pattern'; -import { DuplicateField } from '../../../../../../plugins/kibana_utils/public'; +import { DuplicateField } from '../../../../kibana_utils/public'; // @ts-ignore -import mockLogStashFields from '../../../../../../fixtures/logstash_fields'; +import mockLogStashFields from '../../../../../fixtures/logstash_fields'; // @ts-ignore - -import { stubbedSavedObjectIndexPattern } from '../../../../../../fixtures/stubbed_saved_object_index_pattern'; -import { Field } from '../index_patterns_service'; +import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; +import { Field } from '../fields'; import { setNotifications, setFieldFormats } from '../services'; // Temporary disable eslint, will be removed after moving to new platform folder // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { notificationServiceMock } from '../../../../../../core/public/notifications/notifications_service.mock'; -import { FieldFormatRegisty } from '../../../../../../plugins/data/public'; +import { notificationServiceMock } from '../../../../../core/public/notifications/notifications_service.mock'; +import { FieldFormatRegisty } from '../../field_formats_provider'; jest.mock('ui/new_platform'); -jest.mock('../../../../../../plugins/kibana_utils/public', () => { - const originalModule = jest.requireActual('../../../../../../plugins/kibana_utils/public'); +jest.mock('../../../../kibana_utils/public', () => { + const originalModule = jest.requireActual('../../../../kibana_utils/public'); return { ...originalModule, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts similarity index 96% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index de364b6c217dd..2c93c0aa9dc62 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -26,17 +26,13 @@ import { expandShorthand, FieldMappingSpec, MappingObject, -} from '../../../../../../plugins/kibana_utils/public'; +} from '../../../../kibana_utils/public'; -import { - ES_FIELD_TYPES, - KBN_FIELD_TYPES, - IIndexPattern, - indexPatterns, -} from '../../../../../../plugins/data/public'; - -import { findIndexPatternByTitle, getRoutes } from '../utils'; -import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; + +import { findByTitle, getRoutes } from '../utils'; +import { indexPatterns } from '../'; +import { Field, FieldList, FieldListInterface } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; @@ -46,11 +42,6 @@ import { getNotifications, getFieldFormats } from '../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; -/** @deprecated - * Please use IIndexPattern instead - * */ -export type StaticIndexPattern = IIndexPattern; - export class IndexPattern implements IIndexPattern { [key: string]: any; @@ -296,7 +287,7 @@ export class IndexPattern implements IIndexPattern { await this.save(); } - removeScriptedField(field: FieldType) { + removeScriptedField(field: IFieldType) { this.fields.remove(field); return this.save(); } @@ -384,10 +375,7 @@ export class IndexPattern implements IIndexPattern { return response.id; }; - const potentialDuplicateByTitle = await findIndexPatternByTitle( - this.savedObjectsClient, - this.title - ); + const potentialDuplicateByTitle = await findByTitle(this.savedObjectsClient, this.title); // If there is potentially duplicate title, just create it if (!potentialDuplicateByTitle) { return await _create(); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts similarity index 92% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts index 06933dc409052..2d3e357e96819 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { setup } from '../../../../../../test_utils/public/http_test_setup'; +import { setup } from 'test_utils/http_test_setup'; export const { http } = setup(injectedMetadata => { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts similarity index 97% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 87dd7a68e3061..961e519338ac4 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -18,7 +18,7 @@ */ import { HttpServiceBase } from 'src/core/public'; -import { indexPatterns } from '../../../../../../plugins/data/public'; +import { indexPatterns } from '../'; const API_BASE_URL: string = `/api/index_patterns/`; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts similarity index 87% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts rename to src/plugins/data/public/index_patterns/index_patterns_service.mock.ts index db1ece78e7b4d..d38e9c76430eb 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts +++ b/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts @@ -17,8 +17,9 @@ * under the License. */ -import { IndexPatternsService, IndexPatternsSetup } from '.'; +import { IndexPatternsService } from './index_patterns_service'; import { flattenHitWrapper } from './index_patterns'; +import { IndexPatternsSetup } from './types'; type IndexPatternsServiceClientContract = PublicMethodsOf; @@ -33,11 +34,7 @@ const createSetupContractMock = () => { flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper), formatHitProvider: jest.fn(), indexPatterns: jest.fn() as any, - __LEGACY: { - // For BWC we must temporarily export the class implementation of Field, - // which is only used externally by the Index Pattern UI. - FieldImpl: jest.fn(), - }, + IndexPatternSelect: jest.fn(), }; return setupContract; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/plugins/data/public/index_patterns/index_patterns_service.ts similarity index 60% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts rename to src/plugins/data/public/index_patterns/index_patterns_service.ts index 83738ffe5b747..40f833868847b 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/public/index_patterns/index_patterns_service.ts @@ -23,19 +23,11 @@ import { HttpServiceBase, NotificationsStart, } from 'src/core/public'; -import { FieldFormatsStart } from '../../../../../plugins/data/public'; -import { Field, FieldList, FieldListInterface, FieldType } from './fields'; +import { FieldFormatsStart } from '../field_formats_provider'; import { setNotifications, setFieldFormats } from './services'; +import { IndexPatterns } from './index_patterns'; -import { - createFlattenHitWrapper, - formatHitProvider, - IndexPattern, - IndexPatterns, - StaticIndexPattern, -} from './index_patterns'; - -export interface IndexPatternDependencies { +interface IndexPatternDependencies { uiSettings: IUiSettingsClient; savedObjectsClient: SavedObjectsClientContract; http: HttpServiceBase; @@ -52,16 +44,8 @@ export class IndexPatternsService { private setupApi: any; public setup() { - this.setupApi = { - FieldList, - flattenHitWrapper: createFlattenHitWrapper(), - formatHitProvider, - __LEGACY: { - // For BWC we must temporarily export the class implementation of Field, - // which is only used externally by the Index Pattern UI. - FieldImpl: Field, - }, - }; + this.setupApi = {}; + return this.setupApi; } @@ -76,7 +60,6 @@ export class IndexPatternsService { setFieldFormats(fieldFormats); return { - ...this.setupApi, indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), }; } @@ -85,20 +68,3 @@ export class IndexPatternsService { // nothing to do here yet } } - -// static code - -/** @public */ -export { getFromSavedObject, getRoutes } from './utils'; - -// types - -/** @internal */ -export type IndexPatternsSetup = ReturnType; -export type IndexPatternsStart = ReturnType; - -/** @public */ -export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface }; - -/** @public */ -export { findIndexPatternByTitle } from './utils'; diff --git a/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts b/src/plugins/data/public/index_patterns/lib/get_title.ts similarity index 96% rename from src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts rename to src/plugins/data/public/index_patterns/lib/get_title.ts index 777a12c7e2884..320205f5139c9 100644 --- a/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts +++ b/src/plugins/data/public/index_patterns/lib/get_title.ts @@ -19,7 +19,7 @@ import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -export async function getIndexPatternTitle( +export async function getTitle( client: SavedObjectsClientContract, indexPatternId: string ): Promise> { diff --git a/src/plugins/data/public/index_patterns/lib/index.ts b/src/plugins/data/public/index_patterns/lib/index.ts index 3b87d91bb9fff..0f667e846af91 100644 --- a/src/plugins/data/public/index_patterns/lib/index.ts +++ b/src/plugins/data/public/index_patterns/lib/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export { getIndexPatternTitle } from './get_index_pattern_title'; +export { getTitle } from './get_title'; export * from './types'; export { validateIndexPattern } from './validate_index_pattern'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/services.ts b/src/plugins/data/public/index_patterns/services.ts similarity index 87% rename from src/legacy/core_plugins/data/public/index_patterns/services.ts rename to src/plugins/data/public/index_patterns/services.ts index ecd898b28de63..a6844cb4c04e2 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/services.ts +++ b/src/plugins/data/public/index_patterns/services.ts @@ -18,8 +18,8 @@ */ import { NotificationsStart } from 'src/core/public'; -import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; -import { FieldFormatsStart } from '../../../../../plugins/data/public'; +import { createGetterSetter } from '../../../kibana_utils/public'; +import { FieldFormatsStart } from '../field_formats_provider'; export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' diff --git a/src/plugins/data/public/index_patterns/types.ts b/src/plugins/data/public/index_patterns/types.ts new file mode 100644 index 0000000000000..59e3075bf215d --- /dev/null +++ b/src/plugins/data/public/index_patterns/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IndexPatternsService } from './index_patterns_service'; + +export type IndexPatternsSetup = ReturnType; +export type IndexPatternsStart = ReturnType; diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/plugins/data/public/index_patterns/utils.ts similarity index 93% rename from src/legacy/core_plugins/data/public/index_patterns/utils.ts rename to src/plugins/data/public/index_patterns/utils.ts index 0d0d5705a0ccc..8edb62f16bfeb 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts +++ b/src/plugins/data/public/index_patterns/utils.ts @@ -18,8 +18,7 @@ */ import { find, get } from 'lodash'; - -import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; +import { SavedObjectsClientContract, SimpleSavedObject } from 'src/core/public'; /** * Returns an object matching a given title @@ -28,7 +27,7 @@ import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../co * @param title {string} * @returns {Promise} */ -export async function findIndexPatternByTitle( +export async function findByTitle( client: SavedObjectsClientContract, title: string ): Promise | void> { diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index ceb57b4a3a564..19c21ab9934ef 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -19,6 +19,7 @@ import { FieldFormatRegisty, Plugin, FieldFormatsStart, FieldFormatsSetup } from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; +import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; @@ -53,6 +54,7 @@ const createSetupContract = (): Setup => { search: searchSetupMock, fieldFormats: fieldFormatsMock as FieldFormatsSetup, query: querySetupMock, + indexPatterns: indexPatternsServiceMock.createSetupContract(), }; return setupContract; @@ -69,6 +71,7 @@ const createStartContract = (): Start => { ui: { IndexPatternSelect: jest.fn(), }, + indexPatterns: indexPatternsServiceMock.createStartContract(), }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 35d8edc488467..c018efcad74e6 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -26,8 +26,10 @@ import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; +import { IndexPatternsService } from './index_patterns'; export class DataPublicPlugin implements Plugin { + private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); private readonly autocomplete = new AutocompleteProviderRegister(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; @@ -54,19 +56,30 @@ export class DataPublicPlugin implements Plugin; }; + indexPatterns: IndexPatternsStart; } export * from './autocomplete_provider/types'; diff --git a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index f41024ed16191..ad453c4e5d11d 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -22,7 +22,7 @@ import React, { Component } from 'react'; import { EuiComboBox } from '@elastic/eui'; import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -import { getIndexPatternTitle } from '../../index_patterns/lib'; +import { getTitle } from '../../index_patterns/lib'; export interface IndexPatternSelectProps { onChange: (opt: any) => void; @@ -104,7 +104,7 @@ export class IndexPatternSelect extends Component { let indexPatternTitle; try { - indexPatternTitle = await getIndexPatternTitle(this.props.savedObjectsClient, indexPatternId); + indexPatternTitle = await getTitle(this.props.savedObjectsClient, indexPatternId); } catch (err) { // index pattern no longer exists return; diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap index 979b12847097a..07e2dc7f35f80 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap +++ b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap @@ -13,46 +13,43 @@ exports[`is rendered 1`] = ` In full screen mode, press ESC to exit.

-
- + - + + + + +
diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss b/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss index c42d8b7c4a66d..e810fe0ccdba6 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss +++ b/src/plugins/kibana_react/public/exit_full_screen_button/_exit_full_screen_button.scss @@ -1,17 +1,9 @@ -.dshExitFullScreenButton { - text-align: center; - width: 100%; - height: 0; - bottom: 0; - position: absolute; -} - /** * 1. override the z-index: 1 applied to all non-eui elements that are in :focus via kui * - see packages/kbn-ui-framework/src/global_styling/reset/_reset.scss */ -.dshExitFullScreenButton__mode { +.dshExitFullScreenButton { height: $euiSizeXXL; left: 0; bottom: 0; @@ -59,18 +51,19 @@ color: $euiColorEmptyShade; line-height: $euiSizeXXL; display: inline-block; + font-size: $euiFontSizeS; height: $euiSizeXXL; position: absolute; left: calc(100% + #{$euiSize}); /* 1 */ top: 0px; bottom: 0px; white-space: nowrap; - padding: 0px $euiSizeS 0px $euiSizeM; + padding: 0px $euiSizeXS 0px $euiSizeM; transition: all .2s ease; transform: translateX(-100%); z-index: -1; - - .kuiIcon { - margin-left: $euiSizeS; + + .euiIcon { + margin-left: $euiSizeXS; } } diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index 09e702c55ac78..5ce508ec1ed5b 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -20,9 +20,7 @@ import { i18n } from '@kbn/i18n'; import React, { PureComponent } from 'react'; import { EuiScreenReaderOnly, keyCodes } from '@elastic/eui'; - -// @ts-ignore -import { KuiButton } from '@kbn/ui-framework/components'; +import { EuiIcon } from '@elastic/eui'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; @@ -53,16 +51,15 @@ class ExitFullScreenButtonUi extends PureComponent { })}

-
- +
); diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index b41ebe3e61861..b76da4b4eca3e 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -21,7 +21,7 @@ import sinon from 'sinon'; // TODO: We should not be importing from the data plugin directly here; this is only necessary // because it is one of the few places that we need to access the IndexPattern class itself, rather // than just the type. Doing this as a temporary measure; it will be left behind when migrating to NP. -import { IndexPattern } from '../../legacy/core_plugins/data/public/index_patterns/index_patterns'; +import { IndexPattern } from '../../legacy/core_plugins/data/public/'; import { FieldList, getRoutes, diff --git a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts new file mode 100644 index 0000000000000..7d3e107a350bf --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts @@ -0,0 +1,215 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import yaml from 'js-yaml'; +import axios, { AxiosRequestConfig, AxiosError } from 'axios'; +import fs from 'fs'; +import { union, difference } from 'lodash'; +import path from 'path'; +import { argv } from 'yargs'; + +const config = yaml.safeLoad( + fs.readFileSync( + path.join(__filename, '../../../../../../../config/kibana.dev.yml'), + 'utf8' + ) +); + +const GITHUB_USERNAME = argv.username as string; +const KIBANA_INDEX = config['kibana.index'] as string; +const ELASTICSEARCH_USERNAME = (argv.esUsername as string) || 'elastic'; +const ELASTICSEARCH_PASSWORD = (argv.esPassword || + config['elasticsearch.password']) as string; +const KIBANA_BASE_URL = (argv.baseUrl as string) || 'http://localhost:5601'; + +interface User { + username: string; + roles: string[]; + full_name?: string; + email?: string; + enabled?: boolean; +} + +init().catch(e => { + if (e.response) { + console.log( + JSON.stringify({ request: e.config, response: e.response.data }, null, 2) + ); + return; + } + + console.log(e); +}); + +async function init() { + // kibana.index must be different from `.kibana` + if (KIBANA_INDEX === '.kibana') { + console.log( + 'Please use a custom `kibana.index` in kibana.dev.yml. Example: "kibana.index: .kibana-john"' + ); + return; + } + + if (!KIBANA_INDEX.startsWith('.kibana')) { + console.log( + 'Your `kibana.index` must be prefixed with `.kibana`. Example: "kibana.index: .kibana-john"' + ); + return; + } + + if (!GITHUB_USERNAME) { + console.log( + 'Please specify your github username with `--username ` ' + ); + return; + } + + const KIBANA_READ_ROLE = `kibana_read_${GITHUB_USERNAME}`; + const KIBANA_WRITE_ROLE = `kibana_write_${GITHUB_USERNAME}`; + + // create roles + await createRole({ roleName: KIBANA_READ_ROLE, privilege: 'read' }); + await createRole({ roleName: KIBANA_WRITE_ROLE, privilege: 'all' }); + + // read/write access to all apps + apm index access + await createOrUpdateUser({ + username: 'apm_write_user', + roles: ['apm_user', KIBANA_WRITE_ROLE] + }); + + // read access to all apps + apm index access + await createOrUpdateUser({ + username: 'apm_read_user', + roles: ['apm_user', KIBANA_READ_ROLE] + }); + + // read/write access to all apps (no apm index access) + await createOrUpdateUser({ + username: 'kibana_write_user', + roles: [KIBANA_WRITE_ROLE] + }); +} + +async function callKibana(options: AxiosRequestConfig): Promise { + const { data } = await axios.request({ + ...options, + baseURL: KIBANA_BASE_URL, + auth: { + username: ELASTICSEARCH_USERNAME, + password: ELASTICSEARCH_PASSWORD + }, + headers: { 'kbn-xsrf': 'true', ...options.headers } + }); + return data; +} + +async function createRole({ + roleName, + privilege +}: { + roleName: string; + privilege: 'read' | 'all'; +}) { + const role = await getRole(roleName); + if (role) { + console.log(`Skipping: Role "${roleName}" already exists`); + return; + } + + await callKibana({ + method: 'PUT', + url: `/api/security/role/${roleName}`, + data: { + metadata: { version: 1 }, + elasticsearch: { cluster: [], indices: [] }, + kibana: [{ base: [privilege], feature: {}, spaces: ['*'] }] + } + }); + + console.log(`Created role "${roleName}" with privilege "${privilege}"`); +} + +async function createOrUpdateUser(newUser: User) { + const existingUser = await getUser(newUser.username); + if (!existingUser) { + return createUser(newUser); + } + + return updateUser(existingUser, newUser); +} + +async function createUser(newUser: User) { + const user = await callKibana({ + method: 'POST', + url: `/api/security/v1/users/${newUser.username}`, + data: { + ...newUser, + enabled: true, + password: ELASTICSEARCH_PASSWORD + } + }); + + console.log(`User "${newUser.username}" was created`); + return user; +} + +async function updateUser(existingUser: User, newUser: User) { + const { username } = newUser; + const allRoles = union(existingUser.roles, newUser.roles); + const hasAllRoles = difference(allRoles, existingUser.roles).length === 0; + if (hasAllRoles) { + console.log( + `Skipping: User "${username}" already has neccesarry roles: "${newUser.roles}"` + ); + return; + } + + // assign role to user + await callKibana({ + method: 'POST', + url: `/api/security/v1/users/${username}`, + data: { ...existingUser, roles: allRoles } + }); + + console.log(`User "${username}" was updated`); +} + +async function getUser(username: string) { + try { + return await callKibana({ + url: `/api/security/v1/users/${username}` + }); + } catch (e) { + const err = e as AxiosError; + + // return empty if user doesn't exist + if (err.response?.status === 404) { + return null; + } + + throw e; + } +} + +async function getRole(roleName: string) { + try { + return await callKibana({ + method: 'GET', + url: `/api/security/role/${roleName}` + }); + } catch (e) { + const err = e as AxiosError; + + // return empty if role doesn't exist + if (err.response?.status === 404) { + return null; + } + + throw e; + } +} diff --git a/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js new file mode 100644 index 0000000000000..a3dc3d66f56a0 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/* + * This script will create two roles + * - a read_only kibana role + * - a read/write kibana role + * + * The two roles will be assigned to the already existing users: `apm_read_user`, `apm_write_user`, `kibana_write_user` + * + * This makes it possible to use the existing cloud users locally + * Usage: node setup-kibana-security.js --username YOUR-GITHUB-USERNAME + ******************************/ + +// compile typescript on the fly +require('@babel/register')({ + extensions: ['.ts'], + plugins: ['@babel/plugin-proposal-optional-chaining'], + presets: [ + '@babel/typescript', + ['@babel/preset-env', { targets: { node: 'current' } }] + ] +}); + +require('./kibana-security/setup-custom-kibana-user-role.ts'); diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts index b1879ec92c131..9a13da65e976a 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public/'; import { SerializedNode, UrlTemplate, diff --git a/x-pack/legacy/plugins/ml/common/types/calendars.ts b/x-pack/legacy/plugins/ml/common/types/calendars.ts new file mode 100644 index 0000000000000..b338924dca52f --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/calendars.ts @@ -0,0 +1,18 @@ +/* + * 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 type CalendarId = string; + +export interface Calendar { + calendar_id: CalendarId; + description: string; + events: any[]; + job_ids: string[]; +} + +export interface UpdateCalendar extends Calendar { + calendarId: CalendarId; +} diff --git a/x-pack/legacy/plugins/ml/common/types/custom_urls.ts b/x-pack/legacy/plugins/ml/common/types/custom_urls.ts index 01a1233fd8687..b6b074934cc63 100644 --- a/x-pack/legacy/plugins/ml/common/types/custom_urls.ts +++ b/x-pack/legacy/plugins/ml/common/types/custom_urls.ts @@ -6,15 +6,20 @@ import { AnomalyRecordDoc } from './anomalies'; -export interface UrlConfig { +/** + * Base Interface for basic custom URL. + */ +export interface BaseUrlConfig { url_name: string; url_value: string; } -export interface KibanaUrlConfig extends UrlConfig { +export interface KibanaUrlConfig extends BaseUrlConfig { time_range: string; } +export type UrlConfig = BaseUrlConfig | KibanaUrlConfig; + export interface CustomUrlAnomalyRecordDoc extends AnomalyRecordDoc { earliest: string; latest: string; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_message_icon/job_message_icon.tsx b/x-pack/legacy/plugins/ml/public/application/components/job_message_icon/job_message_icon.tsx index 7546121250013..a17ee19f78feb 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_message_icon/job_message_icon.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/job_message_icon/job_message_icon.tsx @@ -22,10 +22,10 @@ export const JobIcon: FC = ({ message, showTooltip = false }) => { } let color = 'primary'; - const icon = 'alert'; + let icon = 'alert'; if (message.level === INFO) { - color = 'primary'; + icon = 'iInCircle'; } else if (message.level === WARNING) { color = 'warning'; } else if (message.level === ERROR) { diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 642b4c5649a13..3e388329590ce 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -22,7 +22,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { KBN_FIELD_TYPES, esQuery } from '../../../../../../../../src/plugins/data/public'; +import { KBN_FIELD_TYPES, esQuery, esKuery } from '../../../../../../../../src/plugins/data/public'; import { NavigationMenu } from '../../components/navigation_menu'; import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; @@ -180,12 +180,8 @@ export const Page: FC = () => { const qryString = query.query; let qry; if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { - qry = { - query_string: { - query: qryString, - default_operator: 'AND', - }, - }; + const ast = esKuery.fromKueryExpression(qryString); + qry = esKuery.toElasticsearchQuery(ast, currentIndexPattern); } else { qry = esQuery.luceneStringToDsl(qryString); esQuery.decorateQuery(qry, kibanaConfig.get('query:queryString:options')); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/_index.scss b/x-pack/legacy/plugins/ml/public/application/jobs/_index.scss index 9587b2b210c94..318ba1d54c082 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/_index.scss +++ b/x-pack/legacy/plugins/ml/public/application/jobs/_index.scss @@ -1,2 +1,3 @@ @import 'components/custom_url_editor/index'; @import 'jobs_list/index'; // SASSTODO: Various EUI overwrites throughout this folder +@import 'new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.js.snap b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap similarity index 99% rename from x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.js.snap rename to x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 54e6899d4c9e2..6e768cc301852 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -43,6 +43,7 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe fullWidth={false} isInvalid={false} isLoading={false} + name="label" onChange={[Function]} value="Open Dashboard 1" /> @@ -248,6 +249,7 @@ exports[`CustomUrlEditor renders the editor for a discover type URL with an enti fullWidth={false} isInvalid={false} isLoading={false} + name="label" onChange={[Function]} value="Open Discover" /> @@ -493,6 +495,7 @@ exports[`CustomUrlEditor renders the editor for a discover type URL with valid t fullWidth={false} isInvalid={false} isLoading={false} + name="label" onChange={[Function]} value="Open Discover" /> @@ -738,6 +741,7 @@ exports[`CustomUrlEditor renders the editor for a new dashboard type URL with no fullWidth={false} isInvalid={true} isLoading={false} + name="label" onChange={[Function]} value="" /> @@ -947,6 +951,7 @@ exports[`CustomUrlEditor renders the editor for other type of URL with duplicate fullWidth={false} isInvalid={true} isLoading={false} + name="label" onChange={[Function]} value="Show airline" /> @@ -1061,6 +1066,7 @@ exports[`CustomUrlEditor renders the editor for other type of URL with unique la fullWidth={false} isInvalid={false} isLoading={false} + name="label" onChange={[Function]} value="View airline" /> diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/constants.ts b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/constants.ts index 1017f67946d6b..3c11404072f48 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/constants.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/constants.ts @@ -13,4 +13,6 @@ export const URL_TYPE = { export const TIME_RANGE_TYPE = { AUTO: 'auto', INTERVAL: 'interval', -}; +} as const; + +export type TimeRangeType = typeof TIME_RANGE_TYPE[keyof typeof TIME_RANGE_TYPE]; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.js b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.js deleted file mode 100644 index bbf3c3cfbadce..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.js +++ /dev/null @@ -1,388 +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. - */ - - -/* - * React component for the form for editing a custom URL. - */ - -import PropTypes from 'prop-types'; -import React, { - Component -} from 'react'; - -import { - EuiComboBox, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiRadioGroup, - EuiSelect, - EuiSpacer, - EuiTextArea, - EuiTitle, -} from '@elastic/eui'; - -import { isValidCustomUrlSettingsTimeRange } from './utils'; -import { isValidLabel } from '../../../util/custom_url_utils'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; - -import { - TIME_RANGE_TYPE, - URL_TYPE -} from './constants'; - -function getLinkToOptions() { - return [{ - id: URL_TYPE.KIBANA_DASHBOARD, - label: i18n.translate('xpack.ml.customUrlEditor.kibanaDashboardLabel', { - defaultMessage: 'Kibana dashboard' - }), - }, { - id: URL_TYPE.KIBANA_DISCOVER, - label: i18n.translate('xpack.ml.customUrlEditor.discoverLabel', { - defaultMessage: 'Discover' - }), - }, { - id: URL_TYPE.OTHER, - label: i18n.translate('xpack.ml.customUrlEditor.otherLabel', { - defaultMessage: 'Other' - }), - }]; -} - -export const CustomUrlEditor = injectI18n(class CustomUrlEditor extends Component { - static propTypes = { - customUrl: PropTypes.object, - setEditCustomUrl: PropTypes.func.isRequired, - savedCustomUrls: PropTypes.array.isRequired, - dashboards: PropTypes.array.isRequired, - indexPatterns: PropTypes.array.isRequired, - queryEntityFieldNames: PropTypes.array.isRequired, - }; - - constructor(props) { - super(props); - } - - onLabelChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - setEditCustomUrl({ - ...customUrl, - label: e.target.value - }); - }; - - onTypeChange = (linkType) => { - const { customUrl, setEditCustomUrl } = this.props; - setEditCustomUrl({ - ...customUrl, - type: linkType - }); - }; - - onDashboardChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - const kibanaSettings = customUrl.kibanaSettings; - setEditCustomUrl({ - ...customUrl, - kibanaSettings: { - ...kibanaSettings, - dashboardId: e.target.value - } - }); - }; - - onDiscoverIndexPatternChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - const kibanaSettings = customUrl.kibanaSettings; - setEditCustomUrl({ - ...customUrl, - kibanaSettings: { - ...kibanaSettings, - discoverIndexPatternId: e.target.value - } - }); - }; - - onQueryEntitiesChange = (selectedOptions) => { - const { customUrl, setEditCustomUrl } = this.props; - const selectedFieldNames = selectedOptions.map(option => option.label); - - const kibanaSettings = customUrl.kibanaSettings; - setEditCustomUrl({ - ...customUrl, - kibanaSettings: { - ...kibanaSettings, - queryFieldNames: selectedFieldNames - } - }); - }; - - onOtherUrlValueChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - setEditCustomUrl({ - ...customUrl, - otherUrlSettings: { - urlValue: e.target.value - } - }); - }; - - onTimeRangeTypeChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - const timeRange = customUrl.timeRange; - setEditCustomUrl({ - ...customUrl, - timeRange: { - ...timeRange, - type: e.target.value - } - }); - }; - - onTimeRangeIntervalChange = (e) => { - const { customUrl, setEditCustomUrl } = this.props; - const timeRange = customUrl.timeRange; - setEditCustomUrl({ - ...customUrl, - timeRange: { - ...timeRange, - interval: e.target.value - } - }); - }; - - render() { - const { - customUrl, - savedCustomUrls, - dashboards, - indexPatterns, - queryEntityFieldNames, - intl - } = this.props; - - if (customUrl === undefined) { - return; - } - - const { - label, - type, - timeRange, - kibanaSettings, - otherUrlSettings - } = customUrl; - - const dashboardOptions = dashboards.map((dashboard) => { - return { value: dashboard.id, text: dashboard.title }; - }); - - const indexPatternOptions = indexPatterns.map((indexPattern) => { - return { value: indexPattern.id, text: indexPattern.title }; - }); - - const entityOptions = queryEntityFieldNames.map(fieldName => ({ label: fieldName })); - let selectedEntityOptions = []; - if (kibanaSettings !== undefined && kibanaSettings.queryFieldNames !== undefined) { - const queryFieldNames = kibanaSettings.queryFieldNames; - selectedEntityOptions = queryFieldNames.map(fieldName => ({ label: fieldName })); - } - - const timeRangeOptions = Object.keys(TIME_RANGE_TYPE).map((timeRangeType) => { - return { value: TIME_RANGE_TYPE[timeRangeType], text: TIME_RANGE_TYPE[timeRangeType] }; - }); - - const isInvalidLabel = !isValidLabel(label, savedCustomUrls); - const invalidLabelError = (isInvalidLabel === true) ? [ - intl.formatMessage({ - id: 'xpack.ml.customUrlsEditor.invalidLabelErrorMessage', - defaultMessage: 'A unique label must be supplied' - }) - ] : []; - - const isInvalidTimeRange = !isValidCustomUrlSettingsTimeRange(timeRange); - const invalidIntervalError = (isInvalidTimeRange === true) ? [ - intl.formatMessage({ - id: 'xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage', - defaultMessage: 'Invalid interval format' - }) - ] : []; - - return ( - - -

- -

-
- - - } - className="url-label" - error={invalidLabelError} - isInvalid={isInvalidLabel} - compressed - > - - - } - compressed - > - - - - {type === URL_TYPE.KIBANA_DASHBOARD && - } - compressed - > - - - } - - {type === URL_TYPE.KIBANA_DISCOVER && - } - compressed - > - - - } - - {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && - entityOptions.length > 0 && - } - > - - - } - - {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && - <> - - - - } - className="url-time-range" - compressed - > - - - - {(timeRange.type === TIME_RANGE_TYPE.INTERVAL) && - - } - className="url-time-range" - error={invalidIntervalError} - isInvalid={isInvalidTimeRange} - compressed - > - - - - } - - - } - - {type === URL_TYPE.OTHER && - } - compressed - fullWidth={true} - > - - - } - - - -
- ); - } -}); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.js b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx similarity index 79% rename from x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.js rename to x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx index f3aaea1289679..d1c88d6a75abf 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx @@ -5,39 +5,40 @@ */ // Mock the mlJobService that is used for testing custom URLs. +import { UrlConfig } from '../../../../../common/types/custom_urls'; +import { shallow } from 'enzyme'; + jest.mock('../../../services/job_service.js', () => 'mlJobService'); -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { CustomUrlEditor } from './editor'; -import { - TIME_RANGE_TYPE, - URL_TYPE -} from './constants'; - -function prepareTest(customUrl, setEditCustomUrlFn) { +import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; +import { CustomUrlSettings } from './utils'; +import { IIndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns'; +function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: UrlConfig) => void) { const savedCustomUrls = [ { url_name: 'Show data', time_range: 'auto', - url_value: 'kibana#/discover?_g=(time:(from:\'$earliest$\',mode:absolute,to:\'$latest$\'))&_a=' + - '(index:e532ba80-b76f-11e8-a9dc-37914a458883,query:(language:lucene,query:\'airline:"$airline$"\'))' + url_value: + "kibana#/discover?_g=(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&_a=" + + '(index:e532ba80-b76f-11e8-a9dc-37914a458883,query:(language:lucene,query:\'airline:"$airline$"\'))', }, { url_name: 'Show dashboard', time_range: '1h', - url_value: 'kibana#/dashboard/52ea8840-bbef-11e8-a04d-b1701b2b977e?_g=' + - '(time:(from:\'$earliest$\',mode:absolute,to:\'$latest$\'))&' + - '_a=(filters:!(),query:(language:lucene,query:\'airline:"$airline$"\'))' + url_value: + 'kibana#/dashboard/52ea8840-bbef-11e8-a04d-b1701b2b977e?_g=' + + "(time:(from:'$earliest$',mode:absolute,to:'$latest$'))&" + + '_a=(filters:!(),query:(language:lucene,query:\'airline:"$airline$"\'))', }, { url_name: 'Show airline', time_range: 'auto', - url_value: 'http://airlinecodes.info/airline-code-$airline$' + url_value: 'http://airlinecodes.info/airline-code-$airline$', }, - ]; const dashboards = [ @@ -48,7 +49,7 @@ function prepareTest(customUrl, setEditCustomUrlFn) { const indexPatterns = [ { id: 'pattern1', title: 'Index Pattern 1' }, { id: 'pattern2', title: 'Index Pattern 2' }, - ]; + ] as IIndexPattern[]; const queryEntityFieldNames = ['airline']; @@ -61,22 +62,16 @@ function prepareTest(customUrl, setEditCustomUrlFn) { queryEntityFieldNames, }; - const wrapper = shallowWithIntl( - - ); - - return wrapper; + return shallow(); } - describe('CustomUrlEditor', () => { - const setEditCustomUrl = jest.fn(() => {}); const dashboardUrl = { label: '', timeRange: { type: TIME_RANGE_TYPE.AUTO, - interval: '' + interval: '', }, type: URL_TYPE.KIBANA_DASHBOARD, kibanaSettings: { @@ -89,7 +84,7 @@ describe('CustomUrlEditor', () => { label: 'Open Discover', timeRange: { type: TIME_RANGE_TYPE.INTERVAL, - interval: '' + interval: '', }, type: URL_TYPE.KIBANA_DISCOVER, kibanaSettings: { @@ -102,12 +97,12 @@ describe('CustomUrlEditor', () => { label: 'Show airline', timeRange: { type: TIME_RANGE_TYPE.AUTO, - interval: '' + interval: '', }, type: URL_TYPE.OTHER, otherUrlSettings: { - urlValue: 'https://www.google.co.uk/search?q=airline+code+$airline$' - } + urlValue: 'https://www.google.co.uk/search?q=airline+code+$airline$', + }, }; test('renders the editor for a new dashboard type URL with no label', () => { @@ -118,7 +113,7 @@ describe('CustomUrlEditor', () => { test('renders the editor for a dashboard type URL with a label', () => { const dashboardUrlEdit = { ...dashboardUrl, - label: 'Open Dashboard 1' + label: 'Open Dashboard 1', }; const wrapper = prepareTest(dashboardUrlEdit, setEditCustomUrl); expect(wrapper).toMatchSnapshot(); @@ -134,8 +129,8 @@ describe('CustomUrlEditor', () => { ...discoverUrl, timeRange: { type: TIME_RANGE_TYPE.INTERVAL, - interval: '1h' - } + interval: '1h', + }, }; const wrapper = prepareTest(discoverUrlEdit, setEditCustomUrl); expect(wrapper).toMatchSnapshot(); @@ -149,7 +144,7 @@ describe('CustomUrlEditor', () => { test('renders the editor for other type of URL with unique label', () => { const otherUrlEdit = { ...otherUrl, - label: 'View airline' + label: 'View airline', }; const wrapper = prepareTest(otherUrlEdit, setEditCustomUrl); expect(wrapper).toMatchSnapshot(); @@ -162,5 +157,4 @@ describe('CustomUrlEditor', () => { wrapper.update(); expect(setEditCustomUrl).toHaveBeenCalled(); }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx new file mode 100644 index 0000000000000..a7308514de182 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -0,0 +1,377 @@ +/* + * 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, { ChangeEvent, FC } from 'react'; + +import { + EuiComboBox, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiRadioGroup, + EuiSelect, + EuiSpacer, + EuiTextArea, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { isValidCustomUrlSettingsTimeRange } from './utils'; +import { isValidLabel } from '../../../util/custom_url_utils'; + +import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; +import { UrlConfig } from '../../../../../common/types/custom_urls'; +import { IIndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns'; + +function getLinkToOptions() { + return [ + { + id: URL_TYPE.KIBANA_DASHBOARD, + label: i18n.translate('xpack.ml.customUrlEditor.kibanaDashboardLabel', { + defaultMessage: 'Kibana dashboard', + }), + }, + { + id: URL_TYPE.KIBANA_DISCOVER, + label: i18n.translate('xpack.ml.customUrlEditor.discoverLabel', { + defaultMessage: 'Discover', + }), + }, + { + id: URL_TYPE.OTHER, + label: i18n.translate('xpack.ml.customUrlEditor.otherLabel', { + defaultMessage: 'Other', + }), + }, + ]; +} + +interface CustomUrlEditorProps { + customUrl: any; + setEditCustomUrl: (url: any) => void; + savedCustomUrls: UrlConfig[]; + dashboards: any[]; + indexPatterns: IIndexPattern[]; + queryEntityFieldNames: string[]; +} + +interface EuiComboBoxOption { + label?: string; + value?: string; +} + +/* + * React component for the form for editing a custom URL. + */ +export const CustomUrlEditor: FC = ({ + customUrl, + setEditCustomUrl, + savedCustomUrls, + dashboards, + indexPatterns, + queryEntityFieldNames, +}) => { + if (customUrl === undefined) { + return null; + } + + const onLabelChange = (e: ChangeEvent) => { + setEditCustomUrl({ + ...customUrl, + label: e.target.value, + }); + }; + + const onTypeChange = (linkType: string) => { + setEditCustomUrl({ + ...customUrl, + type: linkType, + }); + }; + + const onDashboardChange = (e: ChangeEvent) => { + const kibanaSettings = customUrl.kibanaSettings; + setEditCustomUrl({ + ...customUrl, + kibanaSettings: { + ...kibanaSettings, + dashboardId: e.target.value, + }, + }); + }; + + const onDiscoverIndexPatternChange = (e: ChangeEvent) => { + const kibanaSettings = customUrl.kibanaSettings; + setEditCustomUrl({ + ...customUrl, + kibanaSettings: { + ...kibanaSettings, + discoverIndexPatternId: e.target.value, + }, + }); + }; + + const onQueryEntitiesChange = (selectedOptions: EuiComboBoxOption[]) => { + const selectedFieldNames = selectedOptions.map(option => option.label); + + const kibanaSettings = customUrl.kibanaSettings; + setEditCustomUrl({ + ...customUrl, + kibanaSettings: { + ...kibanaSettings, + queryFieldNames: selectedFieldNames, + }, + }); + }; + + const onOtherUrlValueChange = (e: ChangeEvent) => { + setEditCustomUrl({ + ...customUrl, + otherUrlSettings: { + urlValue: e.target.value, + }, + }); + }; + + const onTimeRangeTypeChange = (e: ChangeEvent) => { + const timeRange = customUrl.timeRange; + setEditCustomUrl({ + ...customUrl, + timeRange: { + ...timeRange, + type: e.target.value, + }, + }); + }; + + const onTimeRangeIntervalChange = (e: ChangeEvent) => { + const timeRange = customUrl.timeRange; + setEditCustomUrl({ + ...customUrl, + timeRange: { + ...timeRange, + interval: e.target.value, + }, + }); + }; + + const { label, type, timeRange, kibanaSettings, otherUrlSettings } = customUrl; + + const dashboardOptions = dashboards.map(dashboard => { + return { value: dashboard.id, text: dashboard.title }; + }); + + const indexPatternOptions = indexPatterns.map(indexPattern => { + return { value: indexPattern.id, text: indexPattern.title }; + }); + + const entityOptions = queryEntityFieldNames.map(fieldName => ({ label: fieldName })); + let selectedEntityOptions: EuiComboBoxOption[] = []; + if (kibanaSettings !== undefined && kibanaSettings.queryFieldNames !== undefined) { + const queryFieldNames: string[] = kibanaSettings.queryFieldNames; + selectedEntityOptions = queryFieldNames.map(fieldName => ({ label: fieldName })); + } + + const timeRangeOptions = Object.values(TIME_RANGE_TYPE).map(timeRangeType => ({ + value: timeRangeType, + text: timeRangeType, + })); + + const isInvalidLabel = !isValidLabel(label, savedCustomUrls); + const invalidLabelError = isInvalidLabel + ? [ + i18n.translate('xpack.ml.customUrlsEditor.invalidLabelErrorMessage', { + defaultMessage: 'A unique label must be supplied', + }), + ] + : []; + + const isInvalidTimeRange = !isValidCustomUrlSettingsTimeRange(timeRange); + const invalidIntervalError = isInvalidTimeRange + ? [ + i18n.translate('xpack.ml.customUrlsList.invalidIntervalFormatErrorMessage', { + defaultMessage: 'Invalid interval format', + }), + ] + : []; + + return ( + <> + +

+ +

+
+ + + + } + className="url-label" + error={invalidLabelError} + isInvalid={isInvalidLabel} + compressed + > + + + + } + compressed + > + + + + {type === URL_TYPE.KIBANA_DASHBOARD && ( + + } + compressed + > + + + )} + + {type === URL_TYPE.KIBANA_DISCOVER && ( + + } + compressed + > + + + )} + + {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && + entityOptions.length > 0 && ( + + } + > + + + )} + + {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && ( + <> + + + + + } + className="url-time-range" + compressed + > + + + + {timeRange.type === TIME_RANGE_TYPE.INTERVAL && ( + + + } + className="url-time-range" + error={invalidIntervalError} + isInvalid={isInvalidTimeRange} + compressed + > + + + + )} + + + )} + + {type === URL_TYPE.OTHER && ( + + } + compressed + fullWidth={true} + > + + + )} + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.js b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.ts similarity index 99% rename from x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.js rename to x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.ts index b4da34bde22a5..f36b71aa5211a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/index.ts @@ -4,6 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ - export { CustomUrlList } from './list'; export { CustomUrlEditor } from './editor'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx index ffb552da8ecf3..8ffb9d8f96f76 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx @@ -25,7 +25,7 @@ import { getTestUrl } from './utils'; import { parseInterval } from '../../../../../common/util/parse_interval'; import { TIME_RANGE_TYPE } from './constants'; -import { KibanaUrlConfig } from '../../../../../common/types/custom_urls'; +import { UrlConfig, KibanaUrlConfig } from '../../../../../common/types/custom_urls'; import { Job } from '../../new_job/common/job_creator/configs'; function isValidTimeRange(timeRange: KibanaUrlConfig['time_range']): boolean { @@ -40,8 +40,8 @@ function isValidTimeRange(timeRange: KibanaUrlConfig['time_range']): boolean { export interface CustomUrlListProps { job: Job; - customUrls: KibanaUrlConfig[]; - setCustomUrls: (customUrls: KibanaUrlConfig[]) => {}; + customUrls: UrlConfig[]; + setCustomUrls: (customUrls: UrlConfig[]) => void; } /* @@ -82,9 +82,9 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust const timeRange = e.target.value; if (timeRange !== undefined && timeRange.length > 0) { - customUrls[index].time_range = timeRange; + (customUrls[index] as KibanaUrlConfig).time_range = timeRange; } else { - delete customUrls[index].time_range; + delete (customUrls[index] as KibanaUrlConfig).time_range; } setCustomUrls(customUrls); } @@ -133,7 +133,7 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust : []; // Validate the time range. - const timeRange = customUrl.time_range; + const timeRange = (customUrl as KibanaUrlConfig).time_range; const isInvalidTimeRange = !isValidTimeRange(timeRange); const invalidIntervalError = isInvalidTimeRange ? [ @@ -208,7 +208,7 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust isInvalid={isInvalidTimeRange} > onTimeRangeChange(e, index)} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts index ee9312aace119..cce0c7c29912c 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts @@ -4,7 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaUrlConfig } from '../../../../../common/types/custom_urls'; +import { IIndexPattern } from 'src/plugins/data/common'; +import { UrlConfig } from '../../../../../common/types/custom_urls'; import { Job } from '../../new_job/common/job_creator/configs'; +import { TimeRangeType } from './constants'; -export function getTestUrl(job: Job, customUrl: KibanaUrlConfig): Promise; +export interface TimeRange { + type: TimeRangeType; + interval: string; +} + +export interface CustomUrlSettings { + label: string; + type: string; + // Note timeRange is only editable in new URLs for Dashboard and Discover URLs, + // as for other URLs we have no way of knowing how the field will be used in the URL. + timeRange: TimeRange; + kibanaSettings?: any; + otherUrlSettings?: { + urlValue: string; + }; +} + +export function getTestUrl(job: Job, customUrl: UrlConfig): Promise; + +export function isValidCustomUrlSettingsTimeRange(timeRangeSettings: any): boolean; + +export function getNewCustomUrlDefaults( + job: Job, + dashboards: any[], + indexPatterns: IIndexPattern[] +): CustomUrlSettings; +export function getQueryEntityFieldNames(job: Job): string[]; +export function isValidCustomUrlSettings( + settings: CustomUrlSettings, + savedCustomUrls: UrlConfig[] +): boolean; +export function buildCustomUrlFromSettings(settings: CustomUrlSettings): Promise; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js index 06391ad7895cb..03cf99172d17c 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js @@ -351,7 +351,7 @@ export function getTestUrl(job, customUrl) { }); } else { - if (response.hits.total > 0) { + if (response.hits.total.value > 0) { testDoc = response.hits.hits[0]._source; } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts new file mode 100644 index 0000000000000..f596bd926f89e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts @@ -0,0 +1,10 @@ +/* + * 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 { IIndexPattern } from 'src/plugins/data/common'; + +export function loadSavedDashboards(maxNumber: number): Promise; +export function loadIndexPatterns(maxNumber: number): Promise; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index c1c98cddaf368..4e38af3c4e1d5 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -236,11 +236,10 @@ function processCustomSettings(jobData, datafeedData) { if (jobData.custom_settings !== undefined) { customSettings = { ...jobData.custom_settings }; - if (jobData.custom_settings.created_by !== undefined) { - if (jobData.detectors !== undefined || Object.keys(datafeedData).length || - (jobData.custom_settings.custom_urls !== undefined && jobData.custom_settings.custom_urls.length)) { - processCreatedBy(customSettings); - } + if (jobData.custom_settings.created_by !== undefined && + (jobData.detectors !== undefined || Object.keys(datafeedData).length) + ) { + processCreatedBy(customSettings); } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.js deleted file mode 100644 index cd3fbff6af6a7..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.js +++ /dev/null @@ -1,259 +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 PropTypes from 'prop-types'; -import React, { - Component -} from 'react'; - -import { - EuiButton, - EuiButtonEmpty, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, -} from '@elastic/eui'; - -import { toastNotifications } from 'ui/notify'; - -import { - CustomUrlEditor, - CustomUrlList, -} from '../../../../components/custom_url_editor'; -import { - getNewCustomUrlDefaults, - getQueryEntityFieldNames, - isValidCustomUrlSettings, - buildCustomUrlFromSettings, - getTestUrl, -} from '../../../../components/custom_url_editor/utils'; -import { - loadSavedDashboards, - loadIndexPatterns, -} from '../edit_utils'; -import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; - -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; - -const MAX_NUMBER_DASHBOARDS = 1000; -const MAX_NUMBER_INDEX_PATTERNS = 1000; - -class CustomUrlsUI extends Component { - constructor(props) { - super(props); - - this.state = { - customUrls: [], - dashboards: [], - indexPatterns: [], - queryEntityFieldNames: [], - editorOpen: false, - }; - - this.setCustomUrls = props.setCustomUrls; - this.angularApply = props.angularApply; - } - - static getDerivedStateFromProps(props) { - return { - job: props.job, - customUrls: props.jobCustomUrls, - queryEntityFieldNames: getQueryEntityFieldNames(props.job), - }; - } - - componentDidMount() { - const { intl } = this.props; - - loadSavedDashboards(MAX_NUMBER_DASHBOARDS) - .then((dashboards)=> { - this.setState({ dashboards }); - }) - .catch((resp) => { - console.log('Error loading list of dashboards:', resp); - toastNotifications.addDanger(intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.loadSavedDashboardsErrorNotificationMessage', - defaultMessage: 'An error occurred loading the list of saved Kibana dashboards' - })); - }); - - loadIndexPatterns(MAX_NUMBER_INDEX_PATTERNS) - .then((indexPatterns) => { - this.setState({ indexPatterns }); - }) - .catch((resp) => { - console.log('Error loading list of dashboards:', resp); - toastNotifications.addDanger(intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.loadIndexPatternsErrorNotificationMessage', - defaultMessage: 'An error occurred loading the list of saved index patterns' - })); - }); - } - - editNewCustomUrl = () => { - // Opens the editor for configuring a new custom URL. - this.setState((prevState) => { - const { dashboards, indexPatterns } = prevState; - - return { - editorOpen: true, - editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, indexPatterns) - }; - }); - } - - setEditCustomUrl = (customUrl) => { - this.setState({ - editorSettings: customUrl - }); - } - - addNewCustomUrl = () => { - buildCustomUrlFromSettings(this.state.editorSettings) - .then((customUrl) => { - const customUrls = [...this.state.customUrls, customUrl]; - this.setCustomUrls(customUrls); - this.setState({ editorOpen: false }); - }) - .catch((resp) => { - console.log('Error building custom URL from settings:', resp); - toastNotifications.addDanger(this.props.intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.addNewUrlErrorNotificationMessage', - defaultMessage: 'An error occurred building the new custom URL from the supplied settings' - })); - }); - } - - onTestButtonClick = () => { - const job = this.props.job; - const { intl } = this.props; - buildCustomUrlFromSettings(this.state.editorSettings) - .then((customUrl) => { - getTestUrl(job, customUrl) - .then((testUrl) => { - openCustomUrlWindow(testUrl, customUrl); - }) - .catch((resp) => { - console.log('Error obtaining URL for test:', resp); - toastNotifications.addWarning(intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.getTestUrlErrorNotificationMessage', - defaultMessage: 'An error occurred obtaining the URL to test the configuration' - })); - }); - }) - .catch((resp) => { - console.log('Error building custom URL from settings:', resp); - toastNotifications.addWarning(intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.buildUrlErrorNotificationMessage', - defaultMessage: 'An error occurred building the custom URL for testing from the supplied settings' - })); - }); - } - - closeEditor = () => { - this.setState({ editorOpen: false }); - } - - render() { - const { - customUrls, - editorOpen, - editorSettings, - dashboards, - indexPatterns, - queryEntityFieldNames, - } = this.state; - - const isValidEditorSettings = (editorOpen === true) ? - isValidCustomUrlSettings(editorSettings, customUrls) : true; - - return ( - - - {editorOpen === false ? ( - - this.editNewCustomUrl()} - > - - - - ) : ( - - - this.closeEditor()} - iconType="cross" - aria-label={this.props.intl.formatMessage({ - id: 'xpack.ml.jobsList.editJobFlyout.customUrls.closeEditorAriaLabel', - defaultMessage: 'Close custom URL editor' - })} - className="close-editor-button" - /> - - - - - this.addNewCustomUrl()} - isDisabled={!isValidEditorSettings} - > - - - - - this.onTestButtonClick()} - isDisabled={!isValidEditorSettings} - > - - - - - - - - )} - - - - - ); - } -} -CustomUrlsUI.propTypes = { - job: PropTypes.object.isRequired, - jobCustomUrls: PropTypes.array.isRequired, - setCustomUrls: PropTypes.func.isRequired, -}; - -export const CustomUrls = injectI18n(CustomUrlsUI); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx new file mode 100644 index 0000000000000..ed2c880c1b65f --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -0,0 +1,319 @@ +/* + * 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, { Component } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiOverlayMask, + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalFooter, +} from '@elastic/eui'; +import { toastNotifications } from 'ui/notify'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { i18n } from '@kbn/i18n'; +import { CustomUrlEditor, CustomUrlList } from '../../../../components/custom_url_editor'; +import { + getNewCustomUrlDefaults, + getQueryEntityFieldNames, + isValidCustomUrlSettings, + buildCustomUrlFromSettings, + getTestUrl, + CustomUrlSettings, +} from '../../../../components/custom_url_editor/utils'; +import { loadSavedDashboards, loadIndexPatterns } from '../edit_utils'; +import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; +import { Job } from '../../../../new_job/common/job_creator/configs'; +import { UrlConfig } from '../../../../../../../common/types/custom_urls'; +import { IIndexPattern } from '../../../../../../../../../../../src/plugins/data/common/index_patterns'; + +const MAX_NUMBER_DASHBOARDS = 1000; +const MAX_NUMBER_INDEX_PATTERNS = 1000; + +interface CustomUrlsProps { + job: Job; + jobCustomUrls: UrlConfig[]; + setCustomUrls: (customUrls: UrlConfig[]) => void; + editMode: 'inline' | 'modal'; +} + +interface CustomUrlsState { + customUrls: UrlConfig[]; + dashboards: any[]; + indexPatterns: IIndexPattern[]; + queryEntityFieldNames: string[]; + editorOpen: boolean; + editorSettings?: CustomUrlSettings; +} + +export class CustomUrls extends Component { + constructor(props: CustomUrlsProps) { + super(props); + + this.state = { + customUrls: [], + dashboards: [], + indexPatterns: [], + queryEntityFieldNames: [], + editorOpen: false, + }; + } + + static getDerivedStateFromProps(props: CustomUrlsProps) { + return { + job: props.job, + customUrls: props.jobCustomUrls, + queryEntityFieldNames: getQueryEntityFieldNames(props.job), + }; + } + + componentDidMount() { + loadSavedDashboards(MAX_NUMBER_DASHBOARDS) + .then(dashboards => { + this.setState({ dashboards }); + }) + .catch(resp => { + // eslint-disable-next-line no-console + console.error('Error loading list of dashboards:', resp); + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.jobsList.editJobFlyout.customUrls.loadSavedDashboardsErrorNotificationMessage', + { + defaultMessage: 'An error occurred loading the list of saved Kibana dashboards', + } + ) + ); + }); + + loadIndexPatterns(MAX_NUMBER_INDEX_PATTERNS) + .then(indexPatterns => { + this.setState({ indexPatterns }); + }) + .catch(resp => { + // eslint-disable-next-line no-console + console.error('Error loading list of dashboards:', resp); + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.jobsList.editJobFlyout.customUrls.loadIndexPatternsErrorNotificationMessage', + { + defaultMessage: 'An error occurred loading the list of saved index patterns', + } + ) + ); + }); + } + + editNewCustomUrl = () => { + // Opens the editor for configuring a new custom URL. + this.setState(prevState => { + const { dashboards, indexPatterns } = prevState; + + return { + editorOpen: true, + editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, indexPatterns), + }; + }); + }; + + setEditCustomUrl = (customUrl: CustomUrlSettings) => { + this.setState({ + editorSettings: customUrl, + }); + }; + + addNewCustomUrl = () => { + buildCustomUrlFromSettings(this.state.editorSettings as CustomUrlSettings) + .then(customUrl => { + const customUrls = [...this.state.customUrls, customUrl]; + this.props.setCustomUrls(customUrls); + this.setState({ editorOpen: false }); + }) + .catch((error: any) => { + // eslint-disable-next-line no-console + console.error('Error building custom URL from settings:', error); + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.jobsList.editJobFlyout.customUrls.addNewUrlErrorNotificationMessage', + { + defaultMessage: + 'An error occurred building the new custom URL from the supplied settings', + } + ) + ); + }); + }; + + onTestButtonClick = () => { + const job = this.props.job; + buildCustomUrlFromSettings(this.state.editorSettings as CustomUrlSettings) + .then(customUrl => { + getTestUrl(job, customUrl) + .then(testUrl => { + openCustomUrlWindow(testUrl, customUrl); + }) + .catch(resp => { + // eslint-disable-next-line no-console + console.error('Error obtaining URL for test:', resp); + toastNotifications.addWarning( + i18n.translate( + 'xpack.ml.jobsList.editJobFlyout.customUrls.getTestUrlErrorNotificationMessage', + { + defaultMessage: 'An error occurred obtaining the URL to test the configuration', + } + ) + ); + }); + }) + .catch(resp => { + // eslint-disable-next-line no-console + console.error('Error building custom URL from settings:', resp); + toastNotifications.addWarning( + i18n.translate( + 'xpack.ml.jobsList.editJobFlyout.customUrls.buildUrlErrorNotificationMessage', + { + defaultMessage: + 'An error occurred building the custom URL for testing from the supplied settings', + } + ) + ); + }); + }; + + closeEditor = () => { + this.setState({ editorOpen: false }); + }; + + renderEditor() { + const { + customUrls, + editorOpen, + editorSettings, + dashboards, + indexPatterns, + queryEntityFieldNames, + } = this.state; + + const editMode = this.props.editMode ?? 'inline'; + const editor = ( + + ); + + const isValidEditorSettings = + editorOpen && editorSettings !== undefined + ? isValidCustomUrlSettings(editorSettings, customUrls) + : true; + + const addButton = ( + + + + ); + + const testButton = ( + + + + ); + + return editMode === 'inline' ? ( + + + + {editor} + + + + {addButton} + {testButton} + + + ) : ( + + + + + + + + + {editor} + + + {testButton} + {addButton} + + + + ); + } + + render() { + const { customUrls, editorOpen } = this.state; + const { editMode = 'inline' } = this.props; + + return ( + <> + + {(!editorOpen || editMode === 'modal') && ( + + + + )} + {editorOpen && this.renderEditor()} + + + + ); + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 2bb2fd310d175..76bd108073a55 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -15,6 +15,7 @@ import { ml } from '../../../services/ml_api_service'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { parseInterval } from '../../../../../common/util/parse_interval'; import { i18n } from '@kbn/i18n'; +import { mlCalendarService } from '../../../services/calendar_service'; export function loadFullJob(jobId) { return new Promise((resolve, reject) => { @@ -147,49 +148,53 @@ function showResults(resp, action) { } } -export function cloneJob(jobId) { - loadFullJob(jobId) - .then((job) => { - if(job.custom_settings && job.custom_settings.created_by) { - // if the job is from a wizards, i.e. contains a created_by property - // use tempJobCloningObjects to temporarily store the job - mlJobService.tempJobCloningObjects.job = job; - - if ( - job.data_counts.earliest_record_timestamp !== undefined && - job.data_counts.latest_record_timestamp !== undefined && - job.data_counts.latest_bucket_timestamp !== undefined) { - // if the job has run before, use the earliest and latest record timestamp - // as the cloned job's time range - let start = job.data_counts.earliest_record_timestamp; - let end = job.data_counts.latest_record_timestamp; - - if (job.datafeed_config.aggregations !== undefined) { - // if the datafeed uses aggregations the earliest and latest record timestamps may not be the same - // as the start and end of the data in the index. - const bucketSpanMs = parseInterval(job.analysis_config.bucket_span).asMilliseconds(); - // round down to the start of the nearest bucket - start = Math.floor(job.data_counts.earliest_record_timestamp / bucketSpanMs) * bucketSpanMs; - // use latest_bucket_timestamp and add two bucket spans minus one ms - end = job.data_counts.latest_bucket_timestamp + (bucketSpanMs * 2) - 1; - } - - mlJobService.tempJobCloningObjects.start = start; - mlJobService.tempJobCloningObjects.end = end; +export async function cloneJob(jobId) { + try { + const job = await loadFullJob(jobId); + if (job.custom_settings && job.custom_settings.created_by) { + // if the job is from a wizards, i.e. contains a created_by property + // use tempJobCloningObjects to temporarily store the job + mlJobService.tempJobCloningObjects.job = job; + + if ( + job.data_counts.earliest_record_timestamp !== undefined && + job.data_counts.latest_record_timestamp !== undefined && + job.data_counts.latest_bucket_timestamp !== undefined) { + // if the job has run before, use the earliest and latest record timestamp + // as the cloned job's time range + let start = job.data_counts.earliest_record_timestamp; + let end = job.data_counts.latest_record_timestamp; + + if (job.datafeed_config.aggregations !== undefined) { + // if the datafeed uses aggregations the earliest and latest record timestamps may not be the same + // as the start and end of the data in the index. + const bucketSpanMs = parseInterval(job.analysis_config.bucket_span).asMilliseconds(); + // round down to the start of the nearest bucket + start = Math.floor(job.data_counts.earliest_record_timestamp / bucketSpanMs) * bucketSpanMs; + // use latest_bucket_timestamp and add two bucket spans minus one ms + end = job.data_counts.latest_bucket_timestamp + (bucketSpanMs * 2) - 1; } - } else { - // otherwise use the tempJobCloningObjects - mlJobService.tempJobCloningObjects.job = job; + + mlJobService.tempJobCloningObjects.start = start; + mlJobService.tempJobCloningObjects.end = end; } - window.location.href = '#/jobs/new_job'; - }) - .catch((error) => { - mlMessageBarService.notify.error(error); - toastNotifications.addDanger(i18n.translate('xpack.ml.jobsList.cloneJobErrorMessage', { - defaultMessage: 'Could not clone {jobId}. Job could not be found', - values: { jobId } - })); - }); + } else { + // otherwise use the tempJobCloningObjects + mlJobService.tempJobCloningObjects.job = job; + } + + if (job.calendars) { + mlJobService.tempJobCloningObjects.calendars = await mlCalendarService.fetchCalendarsByIds(job.calendars); + } + + window.location.href = '#/jobs/new_job'; + } catch (error) { + mlMessageBarService.notify.error(error); + toastNotifications.addDanger(i18n.translate('xpack.ml.jobsList.cloneJobErrorMessage', { + defaultMessage: 'Could not clone {jobId}. Job could not be found', + values: { jobId }, + })); + } } export function closeJobs(jobs, finish = () => {}) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts index cf9407e0c9511..4960492eabeb3 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts @@ -4,19 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UrlConfig } from '../../../../../../../common/types/custom_urls'; +import { CREATED_BY_LABEL } from '../util/constants'; + export type JobId = string; export type BucketSpan = string; +export interface CustomSettings { + custom_urls?: UrlConfig[]; + created_by?: CREATED_BY_LABEL; +} + export interface Job { job_id: JobId; analysis_config: AnalysisConfig; analysis_limits?: AnalysisLimits; background_persist_interval?: string; - custom_settings?: any; + custom_settings?: CustomSettings; data_description: DataDescription; description: string; groups: string[]; - calendars?: string[]; model_plot_config?: ModelPlotConfig; model_snapshot_retention_days?: number; renormalization_window_days?: number; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 86a61e84b445c..e11cebe0383cd 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -6,10 +6,11 @@ import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types'; import { IndexPattern } from 'ui/index_patterns'; +import { UrlConfig } from '../../../../../../common/types/custom_urls'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; -import { Job, Datafeed, Detector, JobId, DatafeedId, BucketSpan } from './configs'; +import { Job, Datafeed, Detector, JobId, DatafeedId, BucketSpan, CustomSettings } from './configs'; import { Aggregation, Field } from '../../../../../../common/types/fields'; import { createEmptyJob, createEmptyDatafeed } from './util/default_configs'; import { mlJobService } from '../../../../services/job_service'; @@ -17,6 +18,8 @@ import { JobRunner, ProgressSubscriber } from '../job_runner'; import { JOB_TYPE, CREATED_BY_LABEL, SHARED_RESULTS_INDEX_NAME } from './util/constants'; import { isSparseDataJob } from './util/general'; import { parseInterval } from '../../../../../../common/util/parse_interval'; +import { Calendar } from '../../../../../../common/types/calendars'; +import { mlCalendarService } from '../../../../services/calendar_service'; export class JobCreator { protected _type: JOB_TYPE = JOB_TYPE.SINGLE_METRIC; @@ -24,6 +27,7 @@ export class JobCreator { protected _savedSearch: SavedSearch; protected _indexPatternTitle: IndexPatternTitle = ''; protected _job_config: Job; + protected _calendars: Calendar[]; protected _datafeed_config: Datafeed; protected _detectors: Detector[]; protected _influencers: string[]; @@ -46,6 +50,7 @@ export class JobCreator { this._indexPatternTitle = indexPattern.title; this._job_config = createEmptyJob(); + this._calendars = []; this._datafeed_config = createEmptyDatafeed(this._indexPatternTitle); this._detectors = this._job_config.analysis_config.detectors; this._influencers = this._job_config.analysis_config.influencers; @@ -188,12 +193,12 @@ export class JobCreator { this._job_config.groups = groups; } - public get calendars(): string[] { - return this._job_config.calendars || []; + public get calendars(): Calendar[] { + return this._calendars; } - public set calendars(calendars: string[]) { - this._job_config.calendars = calendars; + public set calendars(calendars: Calendar[]) { + this._calendars = calendars; } public set modelPlot(enable: boolean) { @@ -358,6 +363,20 @@ export class JobCreator { }); } + /** + * Extends assigned calendars with created job id. + * @private + */ + private async _updateCalendars() { + if (this._calendars.length === 0) { + return; + } + + for (const calendar of this._calendars) { + await mlCalendarService.assignNewJobId(calendar, this.jobId); + } + } + public setTimeRange(start: number, end: number) { this._start = start; this._end = end; @@ -441,6 +460,8 @@ export class JobCreator { public async createJob(): Promise { try { const { success, resp } = await mlJobService.saveNewJob(this._job_config); + await this._updateCalendars(); + if (success === true) { return resp; } else { @@ -486,7 +507,10 @@ export class JobCreator { this._stopAllRefreshPolls.stop = true; } - private _setCustomSetting(setting: string, value: string | object | null) { + private _setCustomSetting( + setting: keyof CustomSettings, + value: CustomSettings[keyof CustomSettings] | null + ) { if (value === null) { // if null is passed in, delete the custom setting if ( @@ -507,12 +531,15 @@ export class JobCreator { [setting]: value, }; } else { + // @ts-ignore this._job_config.custom_settings[setting] = value; } } } - private _getCustomSetting(setting: string): string | object | null { + private _getCustomSetting( + setting: keyof CustomSettings + ): CustomSettings[keyof CustomSettings] | null { if ( this._job_config.custom_settings !== undefined && this._job_config.custom_settings[setting] !== undefined @@ -530,6 +557,14 @@ export class JobCreator { return this._getCustomSetting('created_by') as CREATED_BY_LABEL | null; } + public set customUrls(customUrls: UrlConfig[] | null) { + this._setCustomSetting('custom_urls', customUrls); + } + + public get customUrls(): UrlConfig[] | null { + return this._getCustomSetting('custom_urls') as UrlConfig[] | null; + } + public get formattedJobJson() { return JSON.stringify(this._job_config, null, 2); } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index a73c27d954afe..aaa5142f519ac 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -229,6 +229,8 @@ function stashCombinedJob( mlJobService.tempJobCloningObjects.start = jobCreator.start; mlJobService.tempJobCloningObjects.end = jobCreator.end; } + + mlJobService.tempJobCloningObjects.calendars = jobCreator.calendars; } export function convertToMultiMetricJob(jobCreator: JobCreatorType) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx index ac2d57ea7c8a9..796e88a816529 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx @@ -5,8 +5,9 @@ */ import React, { FC, Fragment } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiAccordion, EuiSpacer } from '@elastic/eui'; +import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { CalendarsSelection } from './components/calendars'; +import { CustomUrlsSelection } from './components/custom_urls'; const ButtonContent = Additional settings; @@ -16,7 +17,6 @@ interface Props { } export const AdditionalSection: FC = ({ additionalExpanded, setAdditionalExpanded }) => { - return null; // disable this section until custom URLs component is ready return ( @@ -27,6 +27,13 @@ export const AdditionalSection: FC = ({ additionalExpanded, setAdditional initialIsOpen={additionalExpanded} > + + + + + + + diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx index f749d78508a2e..c441d1fe6270c 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx @@ -4,24 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useState, useContext, useEffect } from 'react'; -import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; +import React, { FC, useContext, useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButtonIcon, + EuiComboBox, + EuiComboBoxOptionProps, + EuiComboBoxProps, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { JobCreatorContext } from '../../../../../job_creator_context'; import { Description } from './description'; import { ml } from '../../../../../../../../../services/ml_api_service'; +import { Calendar } from '../../../../../../../../../../../common/types/calendars'; export const CalendarsSelection: FC = () => { const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext); - const [selectedCalendars, setSelectedCalendars] = useState(jobCreator.calendars); - const [selectedOptions, setSelectedOptions] = useState([]); - const [options, setOptions] = useState([]); + const [selectedCalendars, setSelectedCalendars] = useState(jobCreator.calendars); + const [selectedOptions, setSelectedOptions] = useState>>( + [] + ); + const [options, setOptions] = useState>>([]); const [isLoading, setIsLoading] = useState(false); async function loadCalendars() { setIsLoading(true); const calendars = await ml.calendars(); - setOptions(calendars.map(c => ({ label: c.calendar_id }))); - setSelectedOptions(selectedCalendars.map(c => ({ label: c }))); + setOptions(calendars.map(c => ({ label: c.calendar_id, value: c }))); + setSelectedOptions(selectedCalendars.map(c => ({ label: c.calendar_id, value: c }))); setIsLoading(false); } @@ -29,25 +45,63 @@ export const CalendarsSelection: FC = () => { loadCalendars(); }, []); - function onChange(optionsIn: EuiComboBoxOptionProps[]) { - setSelectedOptions(optionsIn); - setSelectedCalendars(optionsIn.map(o => o.label)); - } - useEffect(() => { jobCreator.calendars = selectedCalendars; jobCreatorUpdate(); }, [selectedCalendars.join()]); + const comboBoxProps: EuiComboBoxProps = { + async: true, + options, + selectedOptions, + isLoading, + onChange: optionsIn => { + setSelectedOptions(optionsIn); + setSelectedCalendars(optionsIn.map(o => o.value!)); + }, + }; + + const manageCalendarsHref = '#/settings/calendars_list'; + return ( - + + + + + + + } + > + + + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx index b02287c87635f..e11fb615efd70 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx @@ -7,7 +7,10 @@ import React, { memo, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; +import { metadata } from 'ui/metadata'; + +const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-calendars.html`; export const Description: FC = memo(({ children }) => { const title = i18n.translate( @@ -18,16 +21,26 @@ export const Description: FC = memo(({ children }) => { ); return ( {title}} description={ + + + ), + }} /> } > - + <>{children} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx new file mode 100644 index 0000000000000..cd580e60c0843 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext } from 'react'; +import { CustomUrls } from '../../../../../../../../jobs_list/components/edit_job_flyout/tabs/custom_urls'; +import { UrlConfig } from '../../../../../../../../../../../common/types/custom_urls'; +import { JobCreatorContext } from '../../../../../job_creator_context'; +import { Description } from './description'; +import { CombinedJob } from '../../../../../../../common/job_creator/configs'; + +export const CustomUrlsSelection: FC = () => { + const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext); + + const setCustomUrls = (customUrls: UrlConfig[]) => { + jobCreator.customUrls = customUrls; + jobCreatorUpdate(); + }; + + const combinedJob: CombinedJob = { + ...jobCreator.jobConfig, + datafeed_config: jobCreator.datafeedConfig, + }; + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx new file mode 100644 index 0000000000000..89cd5c252c3d6 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -0,0 +1,50 @@ +/* + * 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, { memo, FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; +import { metadata } from 'ui/metadata'; + +const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-configuring-url.html`; + +export const Description: FC = memo(({ children }) => { + const title = i18n.translate( + 'xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.customUrls.title', + { + defaultMessage: 'Custom URLs', + } + ); + return ( + {title}} + description={ + + + + ), + }} + /> + } + > + + <>{children} + + + ); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.scss b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.scss new file mode 100644 index 0000000000000..e71a220974925 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.scss @@ -0,0 +1,12 @@ +.ml-custom-urls-selection { + > .euiFlexGroup { + > .euiFlexItem { + &:first-child { + max-width: 388px; + } + &:last-child { + flex-basis: 50%; + } + } + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.ts new file mode 100644 index 0000000000000..59ca3e4b3ac4c --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { CustomUrlsSelection } from './custom_urls_selection'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index 682ed97020a7f..e086b2b8aad7f 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -86,6 +86,11 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { // auto set the time range based on the index autoSetTimeRange = isAdvancedJobCreator(jobCreator); } + + if (mlJobService.tempJobCloningObjects.calendars) { + jobCreator.calendars = mlJobService.tempJobCloningObjects.calendars; + mlJobService.tempJobCloningObjects.calendars = undefined; + } } else { // creating a new job jobCreator.bucketSpan = DEFAULT_BUCKET_SPAN; diff --git a/x-pack/legacy/plugins/ml/public/application/services/calendar_service.js b/x-pack/legacy/plugins/ml/public/application/services/calendar_service.js deleted file mode 100644 index dafb6b49ad14d..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/services/calendar_service.js +++ /dev/null @@ -1,97 +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 _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -import { ml } from '../services/ml_api_service'; -import { mlJobService } from '../services/job_service'; -import { mlMessageBarService } from '../components/messagebar'; - - - -const msgs = mlMessageBarService; - -class CalendarService { - constructor() { - this.calendars = []; - // list of calendar ids per job id - this.jobCalendars = {}; - // list of calendar ids per group id - this.groupCalendars = {}; - } - - 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; - }); - - 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(i18n.translate('xpack.ml.calendarService.calendarsListCouldNotBeRetrievedErrorMessage', { - defaultMessage: 'Calendars list could not be retrieved' - })); - msgs.error('', err); - reject({ calendars, err }); - }); - }); - } - - // get the list of calendar groups - getCalendarGroups() { - return Object.keys(this.groupCalendars).map(id => ({ id })); - } -} - -export const mlCalendarService = new CalendarService(); diff --git a/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts b/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts new file mode 100644 index 0000000000000..f17c9f359c7b5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts @@ -0,0 +1,54 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ml } from './ml_api_service'; +import { Calendar, CalendarId } from '../../../common/types/calendars'; +import { JobId } from '../jobs/new_job/common/job_creator/configs'; +class CalendarService { + /** + * Assigns a job id to the calendar. + * @param calendar + * @param jobId + */ + async assignNewJobId(calendar: Calendar, jobId: JobId) { + const { calendar_id: calendarId } = calendar; + try { + await ml.updateCalendar({ + ...calendar, + calendarId, + job_ids: [...calendar.job_ids, jobId], + }); + } catch (e) { + throw new Error( + i18n.translate('xpack.ml.calendarService.assignNewJobIdErrorMessage', { + defaultMessage: 'Unable to assign {jobId} to {calendarId}', + values: { calendarId, jobId }, + }) + ); + } + } + + /** + * Fetches calendars by the list of ids. + * @param calendarIds + */ + async fetchCalendarsByIds(calendarIds: CalendarId[]): Promise { + try { + const calendars = await ml.calendars({ calendarIds }); + return Array.isArray(calendars) ? calendars : [calendars]; + } catch (e) { + throw new Error( + i18n.translate('xpack.ml.calendarService.fetchCalendarsByIdsErrorMessage', { + defaultMessage: 'Unable to fetch calendars: {calendarIds}', + values: { calendarIds: calendarIds.join(', ') }, + }) + ); + } + } +} + +export const mlCalendarService = new CalendarService(); diff --git a/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts b/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts index b1d3d338e22c4..a3096a942a7c7 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts @@ -6,6 +6,7 @@ import { SearchResponse } from 'elasticsearch'; import { CombinedJob } from '../jobs/new_job/common/job_creator/configs'; +import { Calendar } from '../../../common/types/calendars'; export interface ExistingJobsAndGroups { jobIds: string[]; @@ -20,6 +21,7 @@ declare interface JobService { skipTimeRangeStep: boolean; start?: number; end?: number; + calendars: Calendar[] | undefined; }; skipTimeRangeStep: boolean; saveNewJob(job: any): Promise; diff --git a/x-pack/legacy/plugins/ml/public/application/services/job_service.js b/x-pack/legacy/plugins/ml/public/application/services/job_service.js index 3db2b6c6dd88e..90aa5d8d66faa 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/job_service.js +++ b/x-pack/legacy/plugins/ml/public/application/services/job_service.js @@ -34,6 +34,7 @@ class JobService { skipTimeRangeStep: false, start: undefined, end: undefined, + calendars: undefined, }; this.jobs = []; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts index 7c0b22b0e1966..a21bce76ddc1f 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts @@ -17,6 +17,7 @@ import { JobMessage } from '../../../../common/types/audit_message'; import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common/analytics'; import { DeepPartial } from '../../../../common/types/common'; import { annotations } from './annotations'; +import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; // TODO This is not a complete representation of all methods of `ml.*`. // It just satisfies needs for other parts of the code area which use @@ -102,14 +103,8 @@ declare interface Ml { setupDataRecognizerConfig(obj: object): Promise; getTimeFieldRange(obj: object): Promise; calculateModelMemoryLimit(obj: object): Promise<{ modelMemoryLimit: string }>; - calendars(): Promise< - Array<{ - calendar_id: string; - description: string; - events: any[]; - job_ids: string[]; - }> - >; + calendars(obj?: { calendarId?: CalendarId; calendarIds?: CalendarId[] }): Promise; + updateCalendar(obj: UpdateCalendar): Promise; getVisualizerFieldStats(obj: object): Promise; getVisualizerOverallStats(obj: object): Promise; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js index 34d9f9ec16f83..fadb05efd12ca 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.js @@ -340,10 +340,21 @@ export const ml = { }); }, - calendars(obj) { - const calendarId = (obj && obj.calendarId) ? `/${obj.calendarId}` : ''; + /** + * Gets a list of calendars + * @param obj + * @returns {Promise} + */ + calendars(obj = {}) { + const { calendarId, calendarIds } = obj; + let calendarIdsPathComponent = ''; + if (calendarId) { + calendarIdsPathComponent = `/${calendarId}`; + } else if (calendarIds) { + calendarIdsPathComponent = `/${calendarIds.join(',')}`; + } return http({ - url: `${basePath}/calendars${calendarId}`, + url: `${basePath}/calendars${calendarIdsPathComponent}`, method: 'GET' }); }, diff --git a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts index 1a15f607e13c2..e2f2dc0ad0fe8 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts @@ -173,14 +173,14 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) for (let i = 0; i < queryFields.length; i++) { const field = queryFields[i]; // Use lodash get to allow nested JSON fields to be retrieved. - const tokenValues: string[] = get(record, field) || null; + const tokenValues: string[] | string | null = get(record, field) || null; if (tokenValues === null) { continue; } // Create a pair `influencerField:value`. // In cases where there are multiple influencer field values for an anomaly // combine values with OR operator e.g. `(influencerField:value or influencerField:another_value)`. - let result = tokenValues + let result = (Array.isArray(tokenValues) ? tokenValues : [tokenValues]) .map(value => `${field}:"${getResultTokenValue(value)}"`) .join(' OR '); result = tokenValues.length > 1 ? `(${result})` : result; diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.js b/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.js index 8b5cbf5b716f2..04e6c7bd52bfc 100644 --- a/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.js +++ b/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.js @@ -53,6 +53,20 @@ export class CalendarManager { } } + /** + * Gets a list of calendar objects based on provided ids. + * @param calendarIds + * @returns {Promise<*>} + */ + async getCalendarsByIds(calendarIds) { + try { + const calendars = await this.getAllCalendars(); + return calendars.filter(calendar => calendarIds.includes(calendar.calendar_id)); + } catch (error) { + throw Boom.badRequest(error); + } + } + async newCalendar(calendar) { const calendarId = calendar.calendarId; const events = calendar.events; @@ -125,5 +139,4 @@ export class CalendarManager { async deleteCalendar(calendarId) { return this.callWithRequest('ml.deleteCalendar', { calendarId }); } - } diff --git a/x-pack/legacy/plugins/ml/server/routes/calendars.js b/x-pack/legacy/plugins/ml/server/routes/calendars.js index 03befdbb4ca9d..cdc8580054c84 100644 --- a/x-pack/legacy/plugins/ml/server/routes/calendars.js +++ b/x-pack/legacy/plugins/ml/server/routes/calendars.js @@ -36,6 +36,12 @@ function deleteCalendar(callWithRequest, calendarId) { return cal.deleteCalendar(calendarId); } +function getCalendarsByIds(callWithRequest, calendarIds) { + const cal = new CalendarManager(callWithRequest); + return cal.getCalendarsByIds(calendarIds); +} + + export function calendars({ commonRouteConfig, elasticsearchPlugin, route }) { route({ @@ -53,12 +59,17 @@ export function calendars({ commonRouteConfig, elasticsearchPlugin, route }) { route({ method: 'GET', - path: '/api/ml/calendars/{calendarId}', + path: '/api/ml/calendars/{calendarIds}', handler(request) { const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - const calendarId = request.params.calendarId; - return getCalendar(callWithRequest, calendarId) - .catch(resp => wrapError(resp)); + const calendarIds = request.params.calendarIds.split(','); + if (calendarIds.length === 1) { + return getCalendar(callWithRequest, calendarIds[0]) + .catch(resp => wrapError(resp)); + } else { + return getCalendarsByIds(callWithRequest, calendarIds) + .catch(resp => wrapError(resp)); + } }, config: { ...commonRouteConfig diff --git a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index 3a872b4c1e327..ebb57d34c01a1 100644 --- a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -19,10 +19,9 @@ import { IEmbeddable, CONTEXT_MENU_TRIGGER, } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; -import { - ISearchEmbeddable, - SEARCH_EMBEDDABLE_TYPE, -} from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable'; +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; +import { ISearchEmbeddable } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/types'; + import { API_BASE_URL_V1 } from '../../common/constants'; const API_BASE_URL = `${API_BASE_URL_V1}/generate/immediate/csv/saved-object`; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ad3fc5544bc3b..4fd99b2c888a3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -659,7 +659,7 @@ "kibana-react.tableListView.listing.table.editActionDescription": "編集", "kibana-react.tableListView.listing.table.editActionName": "編集", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", - "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAreaLabel": "全画面モードを終了", + "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAriaLabel": "全画面モードを終了", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "全画面を終了", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "ESC キーで全画面モードを終了します。", "kibana-react.savedObjects.finder.filterButtonLabel": "タイプ", @@ -871,9 +871,6 @@ "data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} の説明", "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "選択されたクエリボタン {savedQueryName} を保存しました。変更を破棄するには押してください。", "data.search.searchBar.savedQueryPopoverTitleText": "保存されたクエリ", - "devTools.badge.readOnly.text": "読み込み専用", - "devTools.badge.readOnly.tooltip": "を保存できませんでした", - "devTools.k7BreadcrumbsDevToolsLabel": "開発ツール", "data.filter.filterEditor.operatorSelectPlaceholderSelect": "選択してください", "data.filter.filterEditor.operatorSelectPlaceholderWaiting": "待機中", "data.filter.filterEditor.rangeInputLabel": "範囲", @@ -884,6 +881,15 @@ "data.functions.esaggs.help": "AggConfig 集約を実行します", "data.functions.esaggs.inspector.dataRequest.description": "このリクエストは Elasticsearch にクエリし、ビジュアライゼーション用のデータを取得します。", "data.functions.esaggs.inspector.dataRequest.title": "データ", + "data.common.esQuery.kql.errors.endOfInputText": "インプットの終わり", + "data.common.esQuery.kql.errors.fieldNameText": "フィールド名", + "data.common.esQuery.kql.errors.literalText": "文字通り", + "data.common.esQuery.kql.errors.syntaxError": "{expectedList} が予測されましたが {foundInput} が検出されました。", + "data.common.esQuery.kql.errors.valueText": "値", + "data.common.esQuery.kql.errors.whitespaceText": "ホワイトスペース", + "devTools.badge.readOnly.text": "読み込み専用", + "devTools.badge.readOnly.tooltip": "を保存できませんでした", + "devTools.k7BreadcrumbsDevToolsLabel": "開発ツール", "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", "embeddableApi.addPanel.createNewDefaultOption": "新規作成...", "embeddableApi.addPanel.displayName": "パネルの追加", @@ -2421,8 +2427,6 @@ "kbn.visualize.listing.experimentalTitle": "実験的", "kbn.visualize.listing.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", "kbn.visualize.listing.noItemsMessage": "ビジュアライゼーションがないようです。", - "kbn.visualize.listing.noVisualizations.createVisualizationButtonLabel": "ビジュアライゼーションを作成", - "kbn.visualize.listing.noVisualizationsText": "ビジュアライゼーションがないようです。作ってみましょう!", "kbn.visualize.listing.table.entityName": "ビジュアライゼーション", "kbn.visualize.listing.table.entityNamePlural": "ビジュアライゼーション", "kbn.visualize.listing.table.listTitle": "ビジュアライゼーション", @@ -2528,12 +2532,6 @@ "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", "kbnDocViews.table.toggleFieldDetails": "フィールド詳細を切り替える", - "data.common.esQuery.kql.errors.endOfInputText": "インプットの終わり", - "data.common.esQuery.kql.errors.fieldNameText": "フィールド名", - "data.common.esQuery.kql.errors.literalText": "文字通り", - "data.common.esQuery.kql.errors.syntaxError": "{expectedList} が予測されましたが {foundInput} が検出されました。", - "data.common.esQuery.kql.errors.valueText": "値", - "data.common.esQuery.kql.errors.whitespaceText": "ホワイトスペース", "kbnVislibVisTypes.area.areaDescription": "折れ線グラフの下の数量を強調します。", "kbnVislibVisTypes.area.areaTitle": "エリア", "kbnVislibVisTypes.area.groupTitle": "系列を分割", @@ -6839,7 +6837,6 @@ "xpack.ml.calendarsEdit.newEventModal.fromLabel": "開始:", "xpack.ml.calendarsEdit.newEventModal.startDateAriaLabel": "開始日", "xpack.ml.calendarsEdit.newEventModal.toLabel": "終了:", - "xpack.ml.calendarService.calendarsListCouldNotBeRetrievedErrorMessage": "カレンダーリストを取得できませんでした", "xpack.ml.calendarsList.deleteCalendars.calendarsLabel": "{calendarsToDeleteCount} 件のカレンダー", "xpack.ml.calendarsList.deleteCalendars.deletingCalendarErrorMessage": "カレンダー {calendarId} の削除中にエラーが発生しました: {errorMessage}", "xpack.ml.calendarsList.deleteCalendars.deletingCalendarsNotificationMessage": "{messageId} を削除中", @@ -7768,7 +7765,6 @@ "xpack.ml.management.mlTitle": "Machine Learning", "xpack.ml.messagebarService.errorTitle": "エラーが発生しました", "xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "分析", - "xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsSelection.description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam.", "xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsSelection.title": "カレンダー", "xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.enableModelPlot.description": "モデルバウンドのプロットに使用される他のモデル情報を格納するには選択してください。これにより、システムのパフォーマンスにオーバーヘッドが追加されるため、基数の高いデータにはお勧めしません。", "xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.enableModelPlot.title": "モデルプロットを有効にする", @@ -12757,4 +12753,4 @@ "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 14daaa7f78fad..52b67473c1b91 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -646,7 +646,7 @@ "core.euiSuperUpdateButton.updatingButtonLabel": "正在更新", "core.application.appNotFound.pageDescription": "在此 URL 未找到任何应用程序。尝试返回或从菜单中选择应用。", "core.application.appNotFound.title": "未找到应用程序", - "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAreaLabel": "退出全屏模式", + "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAriaLabel": "退出全屏模式", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "退出全屏", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "在全屏模式下,按 ESC 键可退出。", "kibana-react.tableListView.listing.deleteButtonMessage": "删除 {itemCount} 个{entityName}", @@ -872,9 +872,6 @@ "data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} 描述", "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "已保存查询按钮已选择 {savedQueryName}。按下可清除任何更改。", "data.search.searchBar.savedQueryPopoverTitleText": "已保存查询", - "devTools.badge.readOnly.text": "只读", - "devTools.badge.readOnly.tooltip": "无法保存", - "devTools.k7BreadcrumbsDevToolsLabel": "开发工具", "data.filter.filterEditor.operatorSelectPlaceholderSelect": "选择", "data.filter.filterEditor.operatorSelectPlaceholderWaiting": "正在等候", "data.filter.filterEditor.rangeInputLabel": "范围", @@ -885,6 +882,15 @@ "data.functions.esaggs.help": "运行 AggConfig 聚合", "data.functions.esaggs.inspector.dataRequest.description": "此请求将查询 Elasticsearch 以获取用于可视化的数据。", "data.functions.esaggs.inspector.dataRequest.title": "数据", + "data.common.esQuery.kql.errors.endOfInputText": "输入结束", + "data.common.esQuery.kql.errors.fieldNameText": "字段名称", + "data.common.esQuery.kql.errors.literalText": "文本", + "data.common.esQuery.kql.errors.syntaxError": "应为 {expectedList},但却找到了 {foundInput}。", + "data.common.esQuery.kql.errors.valueText": "值", + "data.common.esQuery.kql.errors.whitespaceText": "空白", + "devTools.badge.readOnly.text": "只读", + "devTools.badge.readOnly.tooltip": "无法保存", + "devTools.k7BreadcrumbsDevToolsLabel": "开发工具", "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图", "embeddableApi.addPanel.createNewDefaultOption": "创建新的......", "embeddableApi.addPanel.displayName": "添加面板", @@ -2422,8 +2428,6 @@ "kbn.visualize.listing.experimentalTitle": "实验性", "kbn.visualize.listing.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", "kbn.visualize.listing.noItemsMessage": "看起来您还没有任何可视化。", - "kbn.visualize.listing.noVisualizations.createVisualizationButtonLabel": "创建可视化", - "kbn.visualize.listing.noVisualizationsText": "看起来您还没有任何可视化。开始创建一些吧!", "kbn.visualize.listing.table.entityName": "可视化", "kbn.visualize.listing.table.entityNamePlural": "可视化", "kbn.visualize.listing.table.listTitle": "可视化", @@ -2529,12 +2533,6 @@ "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", "kbnDocViews.table.toggleFieldDetails": "切换字段详细信息", - "data.common.esQuery.kql.errors.endOfInputText": "输入结束", - "data.common.esQuery.kql.errors.fieldNameText": "字段名称", - "data.common.esQuery.kql.errors.literalText": "文本", - "data.common.esQuery.kql.errors.syntaxError": "应为 {expectedList},但却找到了 {foundInput}。", - "data.common.esQuery.kql.errors.valueText": "值", - "data.common.esQuery.kql.errors.whitespaceText": "空白", "kbnVislibVisTypes.area.areaDescription": "突出折线图下方的数量", "kbnVislibVisTypes.area.areaTitle": "面积图", "kbnVislibVisTypes.area.groupTitle": "拆分序列", @@ -6841,7 +6839,6 @@ "xpack.ml.calendarsEdit.newEventModal.fromLabel": "从:", "xpack.ml.calendarsEdit.newEventModal.startDateAriaLabel": "开始日期", "xpack.ml.calendarsEdit.newEventModal.toLabel": "到:", - "xpack.ml.calendarService.calendarsListCouldNotBeRetrievedErrorMessage": "无法检索日历列表", "xpack.ml.calendarsList.deleteCalendars.calendarsLabel": "{calendarsToDeleteCount} 个日历", "xpack.ml.calendarsList.deleteCalendars.deletingCalendarErrorMessage": "删除日历 {calendarId} 时出错。{errorMessage}", "xpack.ml.calendarsList.deleteCalendars.deletingCalendarsNotificationMessage": "正在删除 {messageId}", @@ -7861,7 +7858,6 @@ "xpack.ml.management.mlTitle": "Machine Learning", "xpack.ml.messagebarService.errorTitle": "发生了错误", "xpack.ml.navMenu.dataFrameAnalyticsTabLinkText": "分析", - "xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsSelection.description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam.", "xpack.ml.newJob.wizard.jobDetailsStep.additionalSection.calendarsSelection.title": "日历", "xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.enableModelPlot.description": "选择以存储用于绘制模型边境的其他模型信息。这会增加系统的性能开销,不建议用于高基数数据。", "xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.enableModelPlot.title": "启用模型绘图", @@ -12846,4 +12842,4 @@ "xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。" } -} +} \ No newline at end of file