diff --git a/.eslintignore b/.eslintignore index 86a01b68ecab1..c3921bd22e1ab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,7 +11,6 @@ bower_components /src/plugins/data/common/es_query/kuery/ast/_generated_/** /src/legacy/core_plugins/vis_type_timelion/public/_generated_/** src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data -/src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts /test/fixtures/scenarios /src/legacy/core_plugins/console/public/webpackShims diff --git a/.eslintrc.js b/.eslintrc.js index 199f3743fd621..abfe5e0a6cc27 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -302,6 +302,8 @@ module.exports = { 'test/plugin_functional/plugins/**/public/np_ready/**/*', 'test/plugin_functional/plugins/**/server/np_ready/**/*', 'src/legacy/core_plugins/**/public/np_ready/**/*', + 'src/legacy/core_plugins/vis_type_*/public/**/*', + '!src/legacy/core_plugins/vis_type_*/public/legacy*', 'src/legacy/core_plugins/**/server/np_ready/**/*', 'x-pack/legacy/plugins/**/public/np_ready/**/*', 'x-pack/legacy/plugins/**/server/np_ready/**/*', diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index dc605a47de383..1160b735154dc 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -20,7 +20,9 @@ When you add elements to your workpad, you can: [[add-canvas-element]] === Add elements to your workpad -Choose the elements to display on your workpad, then familiarize yourself with the element using the preconfigured demo data. By default, every element you add to a workpad uses demo data until you change the data source. The demo data includes a small sample data set that you can use to experiment with your element. +Choose the elements to display on your workpad, then familiarize yourself with the element using the preconfigured demo data. By default, most elements use demo data until you change the data source. The demo data includes a small sample data set that you can use to experiment with your element. + +To add a Canvas element: . Click *Add element*. @@ -31,13 +33,26 @@ image::images/canvas-element-select.gif[Canvas elements] . Play around with the default settings and see what the element can do. -TIP: Want to use a different element? You can delete the element by selecting it, clicking the *Element options* icon in the top right, then selecting *Delete*. +To add a map: + +. Click *Embed object*. + +. Select the map you want to add to the workpad. ++ +[role="screenshot"] +image::images/canvas-map-embed.gif[] + +NOTE: Demo data is only supported on Canvas elements. Maps do not support demo data. + +Want to use a different element? You can delete the element by selecting it, clicking the *Element options* icon in the top right, then selecting *Delete*. [float] [[connect-element-data]] -=== Connect the element to your data +=== Connect the Canvas element to your data -When you have finished using the demo data, connect the element to a data source. +When you have finished using the demo data, connect the Canvas element to a data source. + +NOTE: Maps do not support data sources. To change the map data, refer to <>. . Make sure that the element is selected, then select *Data*. @@ -142,7 +157,7 @@ text.align: center; [[configure-auto-refresh-interval]] ==== Change the data auto-refresh interval -Increase or decrease how often your data refreshes on your workpad. +Increase or decrease how often your Canvas element data refreshes on your workpad. . In the top left corner, click the *Control settings* icon. @@ -153,6 +168,17 @@ image::images/canvas-refresh-interval.png[Element data refresh interval] TIP: To manually refresh the data, click the *Refresh data* icon. +[float] +[[canvas-time-range]] +==== Customize map time ranges + +Configure the maps on your workpad for a specific time range. + +From the panel menu, select *Customize time range* to expose a time filter dedicated to the map. + +[role="screenshot"] +image::images/canvas_map-time-filter.gif[] + [float] [[organize-element]] === Organize the elements on your workpad diff --git a/docs/developer/plugin/development-plugin-resources.asciidoc b/docs/developer/plugin/development-plugin-resources.asciidoc index ed6f4b367916e..71c442aaf52e8 100644 --- a/docs/developer/plugin/development-plugin-resources.asciidoc +++ b/docs/developer/plugin/development-plugin-resources.asciidoc @@ -3,10 +3,6 @@ Here are some resources that are helpful for getting started with plugin development. -[float] -==== Our IRC channel -Many Kibana developers hang out on `irc.freenode.net` in the `#kibana` channel. We *want* to help you with plugin development. Even more than that, we *want your help* in understanding your plugin goals, so we can build a great plugin system for you! If you've never used IRC, welcome to the fun. You can get started with the http://webchat.freenode.net/?channels=kibana[Freenode Web Client]. - [float] ==== Some light reading Our {repo}blob/master/CONTRIBUTING.md[contributing guide] can help you get a development environment going. @@ -50,7 +46,7 @@ You're welcome to use these components, but be aware that they are rapidly evolv [float] ==== TypeScript Support -Plugin code can be written in http://www.typescriptlang.org/[TypeScript] if desired. +Plugin code can be written in http://www.typescriptlang.org/[TypeScript] if desired. To enable TypeScript support, create a `tsconfig.json` file at the root of your plugin that looks something like this: ["source","js"] @@ -67,6 +63,6 @@ To enable TypeScript support, create a `tsconfig.json` file at the root of your } ----------- -TypeScript code is automatically converted into JavaScript during development, -but not in the distributable version of Kibana. If you use the +TypeScript code is automatically converted into JavaScript during development, +but not in the distributable version of Kibana. If you use the {repo}blob/{branch}/packages/kbn-plugin-helpers[@kbn/plugin-helpers] to build your plugin, then your `.ts` and `.tsx` files will be permanently transpiled before your plugin is archived. If you have your own build process, make sure to run the TypeScript compiler on your source files and ship the compilation output so that your plugin will work with the distributable version of Kibana. diff --git a/docs/images/canvas-map-embed.gif b/docs/images/canvas-map-embed.gif new file mode 100644 index 0000000000000..eadf521c3b4d1 Binary files /dev/null and b/docs/images/canvas-map-embed.gif differ diff --git a/docs/images/canvas_map-time-filter.gif b/docs/images/canvas_map-time-filter.gif new file mode 100644 index 0000000000000..301d7f4b44158 Binary files /dev/null and b/docs/images/canvas_map-time-filter.gif differ diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 8a10a2bde3b44..9caa3900fccfd 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -220,8 +220,10 @@ might increase the search time. This setting is off by default. Users must opt-i [horizontal] `siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app. `siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. -`siem:enableNewsFeed`:: Enables the News feed -`siem:newsFeedUrl`:: News feed content will be retrieved from this URL +`siem:enableNewsFeed`:: Enables the security news feed on the SIEM *Overview* +page. +`siem:newsFeedUrl`:: The URL from which the security news feed content is +retrieved. `siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds. `siem:timeDefaults`:: The default period of time in the SIEM time filter. diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc index f56baf6abdc2e..a15d860d76775 100644 --- a/docs/siem/index.asciidoc +++ b/docs/siem/index.asciidoc @@ -33,7 +33,8 @@ https://www.elastic.co/products/beats/packetbeat[{packetbeat}] send security events and other data to Elasticsearch. The default index patterns for SIEM events are `auditbeat-*`, `winlogbeat-*`, -`filebeat-*`, `endgame-*`, and `packetbeat-*``. You can change the default index patterns in +`filebeat-*`, `packetbeat-*`, `endgame-*`, and `apm-*-transaction*`. You can +change the default index patterns in *Kibana > Management > Advanced Settings > siem:defaultIndex*. [float] diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index a36b7b9c6d5f5..3906f15167bd0 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -12,6 +12,7 @@ - <> - <> - <> +- <> [[basic-authentication]] ==== Basic authentication @@ -214,3 +215,26 @@ leaked, it can't be re-used after logout. This is known as "local" logout. {kib} can also initiate a "global" logout or _Single Logout_ if it's supported by the external authentication provider and not explicitly disabled by {es}. In this case, the user is redirected to the external authentication provider for log out of all applications associated with the active provider session. + +[[kerberos]] +==== Kerberos single sign-on + +As with the previous SSOs, make sure that you have configured {es} first accordingly. See {ref}/kerberos-realm.html[Kerberos authentication]. + +Next, to enable Kerberos in {kib}, you will need to enable the Kerberos authentication provider in the `kibana.yml` configuration file, as follows: + +[source,yaml] +----------------------------------------------- +xpack.security.authc.providers: [kerberos] +----------------------------------------------- + +You may want to be able to authenticate with the basic authentication provider as a secondary mechanism or while you are setting up Kerberos for the stack: + +[source,yaml] +----------------------------------------------- +xpack.security.authc.providers: [kerberos, basic] +----------------------------------------------- + +As a reminder, the order is important as it determines the order in which each authentication provider is attempted. + +Kibana uses SPNEGO, which wraps the Kerberos protocol for use with HTTP, extending it to web applications. At the end of the Kerberos handshake, Kibana will forward the service ticket to Elasticsearch. Elasticsearch will unpack it and it will respond with an access and refresh token which are then used for subsequent authentication. diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 5fb12ec154952..5c10d89459128 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -211,7 +211,7 @@ export class CoreSystem { const injectedMetadata = await this.injectedMetadata.start(); const uiSettings = await this.uiSettings.start(); const docLinks = await this.docLinks.start({ injectedMetadata }); - const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup! }); + const http = await this.http.start(); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); const fatalErrors = await this.fatalErrors.start(); diff --git a/src/core/public/http/http_service.test.ts b/src/core/public/http/http_service.test.ts index f95d25d116976..a40fcb06273dd 100644 --- a/src/core/public/http/http_service.test.ts +++ b/src/core/public/http/http_service.test.ts @@ -25,13 +25,40 @@ import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.moc import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { HttpService } from './http_service'; +describe('interceptors', () => { + afterEach(() => fetchMock.restore()); + + it('shares interceptors across setup and start', async () => { + fetchMock.get('*', {}); + const injectedMetadata = injectedMetadataServiceMock.createSetupContract(); + const fatalErrors = fatalErrorsServiceMock.createSetupContract(); + const httpService = new HttpService(); + + const setup = httpService.setup({ fatalErrors, injectedMetadata }); + const setupInterceptor = jest.fn(); + setup.intercept({ request: setupInterceptor }); + + const start = httpService.start(); + const startInterceptor = jest.fn(); + start.intercept({ request: startInterceptor }); + + await setup.get('/blah'); + expect(setupInterceptor).toHaveBeenCalledTimes(1); + expect(startInterceptor).toHaveBeenCalledTimes(1); + + await start.get('/other-blah'); + expect(setupInterceptor).toHaveBeenCalledTimes(2); + expect(startInterceptor).toHaveBeenCalledTimes(2); + }); +}); + describe('#stop()', () => { it('calls loadingCount.stop()', () => { const injectedMetadata = injectedMetadataServiceMock.createSetupContract(); const fatalErrors = fatalErrorsServiceMock.createSetupContract(); const httpService = new HttpService(); httpService.setup({ fatalErrors, injectedMetadata }); - httpService.start({ fatalErrors, injectedMetadata }); + httpService.start(); httpService.stop(); expect(loadingServiceMock.stop).toHaveBeenCalled(); }); diff --git a/src/core/public/http/http_service.ts b/src/core/public/http/http_service.ts index 567cdd310cbdf..8965747ba6837 100644 --- a/src/core/public/http/http_service.ts +++ b/src/core/public/http/http_service.ts @@ -35,6 +35,7 @@ interface HttpDeps { export class HttpService implements CoreService { private readonly anonymousPaths = new AnonymousPathsService(); private readonly loadingCount = new LoadingCountService(); + private service?: HttpSetup; public setup({ injectedMetadata, fatalErrors }: HttpDeps): HttpSetup { const kibanaVersion = injectedMetadata.getKibanaVersion(); @@ -42,7 +43,7 @@ export class HttpService implements CoreService { const fetchService = new Fetch({ basePath, kibanaVersion }); const loadingCount = this.loadingCount.setup({ fatalErrors }); - return { + this.service = { basePath, anonymousPaths: this.anonymousPaths.setup({ basePath }), intercept: fetchService.intercept.bind(fetchService), @@ -56,10 +57,16 @@ export class HttpService implements CoreService { put: fetchService.put.bind(fetchService), ...loadingCount, }; + + return this.service; } - public start(deps: HttpDeps) { - return this.setup(deps); + public start() { + if (!this.service) { + throw new Error(`HttpService#setup() must be called first!`); + } + + return this.service; } public stop() { diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx index ba7887b1afa5c..3cf1fe745be8e 100644 --- a/src/core/public/overlays/modal/modal_service.tsx +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -20,7 +20,7 @@ /* eslint-disable max-classes-per-file */ import { i18n as t } from '@kbn/i18n'; -import { EuiModal, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiModal, EuiConfirmModal, EuiOverlayMask, EuiConfirmModalProps } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; @@ -68,6 +68,7 @@ export interface OverlayModalConfirmOptions { className?: string; closeButtonAriaLabel?: string; 'data-test-subj'?: string; + defaultFocusedButton?: EuiConfirmModalProps['defaultFocusedButton']; } /** diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index fb48524c20fb9..aa7ca4fee675e 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -6,6 +6,7 @@ import { Breadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; +import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; import { IconType } from '@elastic/eui'; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 78fc041345577..ef114f51f3100 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -88,7 +88,6 @@ export const IGNORE_DIRECTORY_GLOBS = [ 'src/babel-*', 'packages/*', 'packages/kbn-ui-framework/generator-kui', - 'src/legacy/ui/public/angular-bootstrap', 'src/legacy/ui/public/flot-charts', 'src/legacy/ui/public/utils/lodash-mixins', 'test/functional/fixtures/es_archiver/visualize_source-filters', @@ -124,9 +123,6 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js', 'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json', 'src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js', - 'src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js', - 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html', - 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html', 'src/legacy/ui/public/assets/favicons/android-chrome-192x192.png', 'src/legacy/ui/public/assets/favicons/android-chrome-256x256.png', 'src/legacy/ui/public/assets/favicons/android-chrome-512x512.png', diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts index ba7faf8c34b59..2b21c5c4868a5 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts @@ -35,7 +35,7 @@ import { Schema } from './schemas'; import { ISearchSource, FetchOptions, - fieldFormats, + FieldFormatsContentType, KBN_FIELD_TYPES, } from '../../../../../../plugins/data/public'; @@ -383,7 +383,7 @@ export class AggConfig { return this.aggConfigs.timeRange; } - fieldFormatter(contentType?: fieldFormats.ContentType, defaultFormat?: any) { + fieldFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { const format = this.type && this.type.getFormat(this); if (format) { @@ -393,7 +393,7 @@ export class AggConfig { return this.fieldOwnFormatter(contentType, defaultFormat); } - fieldOwnFormatter(contentType?: fieldFormats.ContentType, defaultFormat?: any) { + fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { const fieldFormatsService = npStart.plugins.data.fieldFormats; const field = this.getField(); let format = field && field.format; diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts index 56299839d0a6d..5ccf0f65c0e92 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts @@ -29,7 +29,7 @@ import { BaseParamType } from './param_types/base'; import { AggParamType } from './param_types/agg'; import { KBN_FIELD_TYPES, - fieldFormats, + IFieldFormat, ISearchSource, } from '../../../../../../plugins/data/public'; @@ -58,7 +58,7 @@ export interface AggTypeConfig< inspectorAdapters: Adapters, abortSignal?: AbortSignal ) => Promise; - getFormat?: (agg: TAggConfig) => fieldFormats.FieldFormat; + getFormat?: (agg: TAggConfig) => IFieldFormat; getValue?: (agg: TAggConfig, bucket: any) => any; getKey?: (bucket: any, key: any, agg: TAggConfig) => any; } @@ -199,7 +199,7 @@ export class AggType< * @param {agg} agg - the agg to pick a format for * @return {FieldFormat} */ - getFormat: (agg: TAggConfig) => fieldFormats.FieldFormat; + getFormat: (agg: TAggConfig) => IFieldFormat; getValue: (agg: TAggConfig, bucket: any) => any; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index e224253a6e314..41e806668337e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { createFilterDateRange } from './date_range'; -import { fieldFormats } from '../../../../../../../../plugins/data/public'; +import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; @@ -28,7 +28,7 @@ jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('Date range', () => { - const getConfig = (() => {}) as fieldFormats.GetConfigFn; + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { name: '@timestamp', diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index 1a78967261fa6..9f845847df5d9 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -20,13 +20,13 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -import { fieldFormats } from '../../../../../../../../plugins/data/public'; +import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('histogram', () => { - const getConfig = (() => {}) as fieldFormats.GetConfigFn; + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { name: 'bytes', diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 2f74f23721813..33344ca0a3484 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -18,7 +18,7 @@ */ import { createFilterRange } from './range'; -import { fieldFormats } from '../../../../../../../../plugins/data/public'; +import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; @@ -27,7 +27,7 @@ jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('range', () => { - const getConfig = (() => {}) as fieldFormats.GetConfigFn; + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { name: 'bytes', diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts index 4c0fa7311461e..b1b0c4bc30a58 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts @@ -19,7 +19,7 @@ import { AggConfigs } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { fieldFormats } from '../../../../../../../plugins/data/public'; +import { FieldFormatsGetConfigFn, fieldFormats } from '../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -44,7 +44,7 @@ const buckets = [ ]; describe('Range Agg', () => { - const getConfig = (() => {}) as fieldFormats.GetConfigFn; + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { name: 'bytes', diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts index b41b16af122fa..0ed44aa876744 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts @@ -30,7 +30,8 @@ import { IAggConfigs } from '../agg_configs'; import { Adapters } from '../../../../../../../plugins/inspector/public'; import { ISearchSource, - fieldFormats, + IFieldFormat, + FieldFormatsContentType, KBN_FIELD_TYPES, } from '../../../../../../../plugins/data/public'; @@ -80,9 +81,9 @@ export const termsBucketAgg = new BucketAggType({ const params = agg.params; return agg.getFieldDisplayName() + ': ' + params.order.text; }, - getFormat(bucket): fieldFormats.FieldFormat { + getFormat(bucket): IFieldFormat { return { - getConverterFor: (type: fieldFormats.ContentType) => { + getConverterFor: (type: FieldFormatsContentType) => { return (val: any) => { if (val === '__other__') { return bucket.params.otherBucketLabel; @@ -94,7 +95,7 @@ export const termsBucketAgg = new BucketAggType({ return bucket.params.field.format.convert(val, type); }; }, - } as fieldFormats.FieldFormat; + } as IFieldFormat; }, createFilter: createFilterTerms, postFlightRequest: async ( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts index ca2dc9d5fb4f5..9c13337a71126 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts @@ -18,34 +18,14 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart, legacyChrome } from './legacy_imports'; -import { LegacyAngularInjectedDependencies } from './plugin'; +import { npSetup, npStart } from './legacy_imports'; import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import './dashboard_config'; import { plugin } from './index'; -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await legacyChrome.dangerouslyGetActiveInjector(); - - return { - dashboardConfig: injector.get('dashboardConfig'), - }; -} - (async () => { const instance = plugin({} as PluginInitializerContext); - instance.setup(npSetup.core, { - ...npSetup.plugins, - npData: npSetup.plugins.data, - __LEGACY: { - getAngularDependencies, - }, - }); + instance.setup(npSetup.core, npSetup.plugins); instance.start(npStart.core, { ...npStart.plugins, data, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b729691831e9a..57edf5e838170 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -30,15 +30,11 @@ export const legacyChrome = chrome; export { SavedObjectSaveOpts } from 'ui/saved_objects/types'; export { npSetup, npStart } from 'ui/new_platform'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -// @ts-ignore -export { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; export { KbnUrl } from 'ui/url/kbn_url'; // @ts-ignore export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; // @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index'; -// @ts-ignore -export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { IInjector } from 'ui/chrome'; export { SavedObjectLoader } from 'ui/saved_objects'; export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index b0e4785edcb0b..e608eb7b7f48c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -17,7 +17,7 @@ * under the License. */ -import { EuiConfirmModal, EuiIcon } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { @@ -30,7 +30,6 @@ import { import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { configureAppAngularModule, - confirmModalFactory, createTopNavDirective, createTopNavHelper, IPrivate, @@ -54,7 +53,7 @@ export interface RenderDeps { navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; savedDashboards: SavedObjectLoader; - dashboardConfig: any; + dashboardConfig: KibanaLegacyStart['dashboardConfig']; dashboardCapabilities: any; uiSettings: IUiSettingsClient; chrome: ChromeStart; @@ -111,7 +110,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav createLocalConfigModule(core); createLocalKbnUrlModule(); createLocalTopNavModule(navigation); - createLocalConfirmModalModule(); createLocalIconModule(); const dashboardAngularModule = angular.module(moduleName, [ @@ -122,7 +120,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/TopNav', 'app/dashboard/KbnUrl', 'app/dashboard/Promise', - 'app/dashboard/ConfirmModal', 'app/dashboard/icon', ]); return dashboardAngularModule; @@ -134,13 +131,6 @@ function createLocalIconModule() { .directive('icon', reactDirective => reactDirective(EuiIcon)); } -function createLocalConfirmModalModule() { - angular - .module('app/dashboard/ConfirmModal', ['react']) - .factory('confirmModal', confirmModalFactory) - .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); -} - function createLocalKbnUrlModule() { angular .module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute']) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index 0537e3f8fc456..ad69ef322a909 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -25,7 +25,7 @@ import { IInjector } from '../legacy_imports'; import { ViewMode } from '../../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; -import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; +import { DashboardAppState, SavedDashboardPanel } from './types'; import { IIndexPattern, TimeRange, @@ -87,8 +87,6 @@ export interface DashboardAppScope extends ng.IScope { export function initDashboardAppDirective(app: any, deps: RenderDeps) { app.directive('dashboardApp', function($injector: IInjector) { - const confirmModal = $injector.get('confirmModal'); - return { restrict: 'E', controllerAs: 'dashboardApp', @@ -105,7 +103,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $route, $scope, $routeParams, - confirmModal, indexPatterns: deps.npDataStart.indexPatterns, kbnUrlStateStorage, history, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 9f6b01d5beb49..0b55adc1d52be 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -19,6 +19,7 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; import React from 'react'; import angular from 'angular'; @@ -27,12 +28,7 @@ import { map } from 'rxjs/operators'; import { History } from 'history'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; -import { - ConfirmationButtonTypes, - migrateLegacyQuery, - SavedObjectSaveOpts, - subscribeWithScope, -} from '../legacy_imports'; +import { migrateLegacyQuery, SavedObjectSaveOpts, subscribeWithScope } from '../legacy_imports'; import { COMPARE_ALL_OPTIONS, compareFilters, @@ -63,7 +59,7 @@ import { openAddPanelFlyout, ViewMode, } from '../../../../embeddable_api/public/np_ready/public'; -import { ConfirmModalFn, NavAction, SavedDashboardPanel } from './types'; +import { NavAction, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; @@ -82,14 +78,14 @@ import { removeQueryParam, unhashUrl, } from '../../../../../../plugins/kibana_utils/public'; +import { KibanaLegacyStart } from '../../../../../../plugins/kibana_legacy/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; $route: any; $routeParams: any; indexPatterns: IndexPatternsContract; - dashboardConfig: any; - confirmModal: ConfirmModalFn; + dashboardConfig: KibanaLegacyStart['dashboardConfig']; history: History; kbnUrlStateStorage: IKbnUrlStateStorage; } @@ -107,7 +103,6 @@ export class DashboardAppController { dashboardConfig, localStorage, indexPatterns, - confirmModal, savedQueryService, embeddables, share, @@ -634,27 +629,31 @@ export class DashboardAppController { } } - confirmModal( - i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', { - defaultMessage: `Once you discard your changes, there's no getting them back.`, - }), - { - onConfirm: revertChangesAndExitEditMode, - onCancel: _.noop, - confirmButtonText: i18n.translate( - 'kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel', - { defaultMessage: 'Discard changes' } - ), - cancelButtonText: i18n.translate( - 'kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel', - { defaultMessage: 'Continue editing' } - ), - defaultFocusedButton: ConfirmationButtonTypes.CANCEL, - title: i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', { - defaultMessage: 'Discard changes to dashboard?', + overlays + .openConfirm( + i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', { + defaultMessage: `Once you discard your changes, there's no getting them back.`, }), - } - ); + { + confirmButtonText: i18n.translate( + 'kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel', + { defaultMessage: 'Discard changes' } + ), + cancelButtonText: i18n.translate( + 'kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel', + { defaultMessage: 'Continue editing' } + ), + defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, + title: i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', { + defaultMessage: 'Discard changes to dashboard?', + }), + } + ) + .then(isConfirmed => { + if (isConfirmed) { + revertChangesAndExitEditMode(); + } + }); }; /** diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts index 3151fbf821b9f..146affda28200 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts @@ -137,15 +137,3 @@ export interface StagedFilter { operator: string; index: string; } - -export type ConfirmModalFn = ( - message: string, - confirmOptions: { - onConfirm: () => void; - onCancel: () => void; - confirmButtonText: string; - cancelButtonText: string; - defaultFocusedButton: string; - title: string; - } -) => void; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 7ae1c723a3914..09ae49f2305fd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -45,30 +45,25 @@ import { SharePluginStart } from '../../../../../plugins/share/public'; import { AngularRenderedAppUpdater, KibanaLegacySetup, + KibanaLegacyStart, } from '../../../../../plugins/kibana_legacy/public'; import { createSavedDashboardLoader } from './saved_dashboard/saved_dashboards'; import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public'; import { getQueryStateContainer } from '../../../../../plugins/data/public'; -export interface LegacyAngularInjectedDependencies { - dashboardConfig: any; -} - export interface DashboardPluginStartDependencies { data: DataStart; npData: NpDataStart; embeddables: IEmbeddableStart; navigation: NavigationStart; share: SharePluginStart; + kibanaLegacy: KibanaLegacyStart; } export interface DashboardPluginSetupDependencies { - __LEGACY: { - getAngularDependencies: () => Promise; - }; home: HomePublicPluginSetup; kibanaLegacy: KibanaLegacySetup; - npData: NpDataSetup; + data: NpDataSetup; } export class DashboardPlugin implements Plugin { @@ -78,6 +73,7 @@ export class DashboardPlugin implements Plugin { embeddables: IEmbeddableStart; navigation: NavigationStart; share: SharePluginStart; + dashboardConfig: KibanaLegacyStart['dashboardConfig']; } | null = null; private appStateUpdater = new BehaviorSubject(() => ({})); @@ -85,12 +81,7 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, - { - __LEGACY: { getAngularDependencies }, - home, - kibanaLegacy, - npData, - }: DashboardPluginSetupDependencies + { home, kibanaLegacy, data: npData }: DashboardPluginSetupDependencies ) { const { querySyncStateContainer, stop: stopQuerySyncStateContainer } = getQueryStateContainer( npData.query @@ -126,8 +117,8 @@ export class DashboardPlugin implements Plugin { navigation, share, npDataStart, + dashboardConfig, } = this.startDependencies; - const angularDependencies = await getAngularDependencies(); const savedDashboards = createSavedDashboardLoader({ savedObjectsClient, indexPatterns: npDataStart.indexPatterns, @@ -137,7 +128,7 @@ export class DashboardPlugin implements Plugin { const deps: RenderDeps = { core: contextCore as LegacyCoreStart, - ...angularDependencies, + dashboardConfig, navigation, share, npDataStart, @@ -186,7 +177,14 @@ export class DashboardPlugin implements Plugin { start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies + { + data: dataStart, + embeddables, + navigation, + npData, + share, + kibanaLegacy: { dashboardConfig }, + }: DashboardPluginStartDependencies ) { this.startDependencies = { npDataStart: npData, @@ -194,6 +192,7 @@ export class DashboardPlugin implements Plugin { embeddables, navigation, share, + dashboardConfig, }; } 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 index cc4dabd123ff4..eb6d7e6467f2f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -21,7 +21,6 @@ // these are necessary to bootstrap the local angular. // They can stay even after NP cutover import angular from 'angular'; -import 'ui/angular-bootstrap'; import { EuiIcon } from '@elastic/eui'; // @ts-ignore import { StateProvider } from 'ui/state_management/state'; @@ -64,6 +63,7 @@ import { createFieldChooserDirective } from './np_ready/components/field_chooser import { createDiscoverFieldDirective } from './np_ready/components/field_chooser/discover_field'; import { CollapsibleSidebarProvider } from './np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar'; import { DiscoverStartPlugins } from './plugin'; +import { initAngularBootstrap } from '../../../../../plugins/kibana_legacy/public'; import { createCssTruncateDirective } from './np_ready/angular/directives/css_truncate'; // @ts-ignore import { FixedScrollProvider } from './np_ready/angular/directives/fixed_scroll'; @@ -85,6 +85,7 @@ import { * 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) { + initAngularBootstrap(); const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data); configureAppAngularModule(module, core as LegacyCoreStart, true); return module; diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index f02ec234e0a83..c4e58e1a5e1ae 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -17,7 +17,6 @@ * under the License. */ -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; import { npSetup, npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; @@ -44,26 +43,12 @@ async function getAngularDependencies(): Promise { const instance = new HomePlugin(); instance.setup(npSetup.core, { ...npSetup.plugins, __LEGACY: { metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - getFeatureCatalogueEntries: async () => { - if (!copiedLegacyCatalogue) { - const injector = await chrome.dangerouslyGetActiveInjector(); - const Private = injector.get('Private'); - // Merge legacy registry with new registry - (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( - npSetup.plugins.home.featureCatalogue.register - ); - copiedLegacyCatalogue = true; - } - return npStart.plugins.home.featureCatalogue.get(); - }, getAngularDependencies, }, }); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 90fb32a88d43c..66c4d995e2566 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -31,14 +31,13 @@ import { import { UiStatsMetricType } from '@kbn/analytics'; import { Environment, - FeatureCatalogueEntry, HomePublicPluginSetup, + FeatureCatalogueEntry, } from '../../../../../plugins/home/public'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; export interface HomeKibanaServices { indexPatternService: any; - getFeatureCatalogueEntries: () => Promise; metadata: { app: unknown; bundleId: string; @@ -58,6 +57,7 @@ export interface HomeKibanaServices { uiSettings: IUiSettingsClient; config: KibanaLegacySetup['config']; homeConfig: HomePublicPluginSetup['config']; + directories: readonly FeatureCatalogueEntry[]; http: HttpStart; savedObjectsClient: SavedObjectsClientContract; toastNotifications: NotificationsSetup['toasts']; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx index 8345491d99972..2149885f3ee11 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx @@ -26,8 +26,7 @@ import { getServices } from '../kibana_services'; export const renderApp = async (element: HTMLElement) => { const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - const { getFeatureCatalogueEntries, chrome } = getServices(); - const directories = await getFeatureCatalogueEntries(); + const { directories, chrome } = getServices(); chrome.setBreadcrumbs([{ text: homeTitle }]); render(, element); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 5802f33627fb3..e530906d5698e 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -25,9 +25,9 @@ import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { Environment, - FeatureCatalogueEntry, HomePublicPluginStart, HomePublicPluginSetup, + FeatureCatalogueEntry, } from '../../../../../plugins/home/public'; export interface LegacyAngularInjectedDependencies { @@ -55,7 +55,6 @@ export interface HomePluginSetupDependencies { devMode: boolean; uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; }; - getFeatureCatalogueEntries: () => Promise; getAngularDependencies: () => Promise; }; usageCollection: UsageCollectionSetup; @@ -67,6 +66,7 @@ export class HomePlugin implements Plugin { private dataStart: DataPublicPluginStart | null = null; private savedObjectsClient: any = null; private environment: Environment | null = null; + private directories: readonly FeatureCatalogueEntry[] | null = null; setup( core: CoreSetup, @@ -100,6 +100,7 @@ export class HomePlugin implements Plugin { environment: this.environment!, config: kibanaLegacy.config, homeConfig: home.config, + directories: this.directories!, ...angularDependencies, }); const { renderApp } = await import('./np_ready/application'); @@ -110,6 +111,7 @@ export class HomePlugin implements Plugin { start(core: CoreStart, { data, home }: HomePluginStartDependencies) { this.environment = home.environment.get(); + this.directories = home.featureCatalogue.get(); this.dataStart = data; this.savedObjectsClient = core.savedObjects.client; } diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index 1305310b6f615..6e5269e11652f 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -18,7 +18,6 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { render, unmountComponentAtNode } from 'react-dom'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -30,10 +29,6 @@ import appTemplate from './app.html'; import landingTemplate from './landing.html'; import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { ManagementSidebarNav } from '../../../../../plugins/management/public'; -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; import { timefilter } from 'ui/timefilter'; import { EuiPageContent, @@ -170,19 +165,3 @@ uiModules.get('apps/management').directive('kbnManagementLanding', function(kbnV }, }; }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'stack-management', - title: i18n.translate('kbn.stackManagement.managementLabel', { - defaultMessage: 'Stack Management', - }), - description: i18n.translate('kbn.stackManagement.managementDescription', { - defaultMessage: 'Your center console for managing the Elastic Stack.', - }), - icon: 'managementApp', - path: '/app/kibana#/management', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js index 77b43a651d548..b5c6000eb2fe1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js @@ -43,6 +43,7 @@ export class CreateIndexPatternWizard extends Component { indexPatternCreationType: PropTypes.object.isRequired, config: PropTypes.object.isRequired, changeUrl: PropTypes.func.isRequired, + openConfirm: PropTypes.func.isRequired, }).isRequired, }; @@ -142,12 +143,16 @@ export class CreateIndexPatternWizard extends Component { values: { title: this.title }, defaultMessage: "An index pattern with the title '{title}' already exists.", }); - try { - await services.confirmModalPromise(confirmMessage, { - confirmButtonText: 'Go to existing pattern', - }); + + const isConfirmed = await services.openConfirm(confirmMessage, { + confirmButtonText: i18n.translate('kbn.management.indexPattern.goToPatternButtonLabel', { + defaultMessage: 'Go to existing pattern', + }), + }); + + if (isConfirmed) { return services.changeUrl(`/management/kibana/index_patterns/${indexPatternId}`); - } catch (err) { + } else { return false; } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index d1087b4575e82..d06bc8784de51 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -43,10 +43,10 @@ uiRoutes.when('/management/kibana/index_pattern', { $http: npStart.core.http, savedObjectsClient: npStart.core.savedObjects.client, indexPatternCreationType, - confirmModalPromise: $injector.get('confirmModalPromise'), changeUrl: url => { $scope.$evalAsync(() => kbnUrl.changePath(url)); }, + openConfirm: npStart.core.overlays.openConfirm, }; const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index eb7358c66e226..0cbac20a947bf 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -198,8 +198,7 @@ uiModules $route, Promise, config, - Private, - confirmModal + Private ) { const { startSyncingState, @@ -290,15 +289,19 @@ uiModules confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { defaultMessage: 'Refresh', }), - onConfirm: async () => { - await $scope.indexPattern.init(true); - $scope.fields = $scope.indexPattern.getNonScriptedFields(); - }, title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { defaultMessage: 'Refresh field list?', }), }; - confirmModal(confirmMessage, confirmModalOptions); + + npStart.core.overlays + .openConfirm(confirmMessage, confirmModalOptions) + .then(async isConfirmed => { + if (isConfirmed) { + await $scope.indexPattern.init(true); + $scope.fields = $scope.indexPattern.getNonScriptedFields(); + } + }); }; $scope.removePattern = function() { @@ -322,12 +325,16 @@ uiModules confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { defaultMessage: 'Delete', }), - onConfirm: doRemove, title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { defaultMessage: 'Delete index pattern?', }), }; - confirmModal('', confirmModalOptions); + + npStart.core.overlays.openConfirm('', confirmModalOptions).then(isConfirmed => { + if (isConfirmed) { + doRemove(); + } + }); }; $scope.setDefaultPattern = function() { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 8ab26f8c0d1c8..310797a7f3a0c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -27,10 +27,6 @@ import indexTemplate from './index.html'; import indexPatternListTemplate from './list.html'; import { IndexPatternTable } from './index_pattern_table'; import { npStart } from 'ui/new_platform'; -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { UICapabilitiesProvider } from 'ui/capabilities/react'; @@ -175,19 +171,3 @@ management.getSection('kibana').register('index_patterns', { order: 0, url: '#/management/kibana/index_patterns/', }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'index_patterns', - title: i18n.translate('kbn.management.indexPatternHeader', { - defaultMessage: 'Index Patterns', - }), - description: i18n.translate('kbn.management.indexPatternLabel', { - defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.', - }), - icon: 'indexPatternApp', - path: '/app/kibana#/management/kibana/index_patterns', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js index c16e4cb00c2bd..e3ab862cd84b7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_objects.js @@ -38,7 +38,6 @@ function updateObjectsTable($scope, $injector) { const $http = $injector.get('$http'); const kbnUrl = $injector.get('kbnUrl'); const config = $injector.get('config'); - const confirmModalPromise = $injector.get('confirmModalPromise'); const savedObjectsClient = npStart.core.savedObjects.client; const services = savedObjectManagementRegistry.all().map(obj => obj.service); @@ -54,7 +53,7 @@ function updateObjectsTable($scope, $injector) { fatalError(error, location)); } const confirmModalOptions = { - onConfirm: doDelete, confirmButtonText: i18n.translate( 'kbn.management.objects.confirmModalOptions.deleteButtonLabel', { @@ -244,12 +244,19 @@ uiModules defaultMessage: 'Delete saved Kibana object?', }), }; - confirmModal( - i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { - defaultMessage: "You can't recover deleted objects", - }), - confirmModalOptions - ); + + overlays + .openConfirm( + i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', { + defaultMessage: "You can't recover deleted objects", + }), + confirmModalOptions + ) + .then(isConfirmed => { + if (isConfirmed) { + doDelete(); + } + }); }; $scope.submit = function() { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js index 7bd57e87bc5c9..3965c42ac088d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/index.js @@ -23,10 +23,6 @@ import './_view'; import './_objects'; import 'ace'; import { uiModules } from 'ui/modules'; -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; // add the module deps to this module uiModules.get('apps/management'); @@ -38,20 +34,3 @@ management.getSection('kibana').register('objects', { order: 10, url: '#/management/kibana/objects', }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'saved_objects', - title: i18n.translate('kbn.management.objects.savedObjectsTitle', { - defaultMessage: 'Saved Objects', - }), - description: i18n.translate('kbn.management.objects.savedObjectsDescription', { - defaultMessage: - 'Import, export, and manage your saved searches, visualizations, and dashboards.', - }), - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js index e3cee4186e278..e13e8c1efe8f7 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js @@ -79,24 +79,25 @@ async function importIndexPattern(doc, indexPatterns, overwriteAll, confirmModal let newId = await emptyPattern.create(overwriteAll); if (!newId) { // We can override and we want to prompt for confirmation - try { - await confirmModalPromise( - i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', { - values: { title: this.title }, - defaultMessage: "Are you sure you want to overwrite '{title}'?", + const isConfirmed = await confirmModalPromise( + i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', { + values: { title: this.title }, + defaultMessage: "Are you sure you want to overwrite '{title}'?", + }), + { + title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', { + defaultMessage: 'Overwrite {type}?', + values: { type }, }), - { - title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', { - defaultMessage: 'Overwrite {type}?', - values: { type }, - }), - confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', { - defaultMessage: 'Overwrite', - }), - } - ); + confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', { + defaultMessage: 'Overwrite', + }), + } + ); + + if (isConfirmed) { newId = await emptyPattern.create(true); - } catch (err) { + } else { return; } } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/index.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/index.js index 6d8987b1a928e..16d70a9f4ed57 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/index.js @@ -23,10 +23,6 @@ import { uiModules } from 'ui/modules'; import { capabilities } from 'ui/capabilities'; import { I18nContext } from 'ui/i18n'; import indexTemplate from './index.html'; -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; import React from 'react'; import { AdvancedSettings } from './advanced_settings'; @@ -83,19 +79,3 @@ management.getSection('kibana').register('settings', { order: 20, url: '#/management/kibana/settings', }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'advanced_settings', - title: i18n.translate('kbn.management.settings.advancedSettingsLabel', { - defaultMessage: 'Advanced Settings', - }), - description: i18n.translate('kbn.management.settings.advancedSettingsDescription', { - defaultMessage: 'Directly edit settings that control behavior in Kibana.', - }), - icon: 'advancedSettingsApp', - path: '/app/kibana#/management/kibana/settings', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index d3a7f6ac1ff7d..ff7d167ccaacd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -46,8 +46,6 @@ export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { EventsProvider } from 'ui/events'; // @ts-ignore export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; // @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts index 222b035708976..44e7e9c2a7413 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts @@ -18,7 +18,6 @@ */ import angular, { IModule } from 'angular'; -import { EuiConfirmModal } from '@elastic/eui'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext, LegacyCoreStart } from 'kibana/public'; @@ -26,7 +25,6 @@ import { AppStateProvider, AppState, configureAppAngularModule, - confirmModalFactory, createTopNavDirective, createTopNavHelper, EventsProvider, @@ -93,7 +91,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav createLocalStateModule(); createLocalPersistedStateModule(); createLocalTopNavModule(navigation); - createLocalConfirmModalModule(); const visualizeAngularModule: IModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, @@ -103,18 +100,10 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/visualize/PersistedState', 'app/visualize/TopNav', 'app/visualize/State', - 'app/visualize/ConfirmModal', ]); return visualizeAngularModule; } -function createLocalConfirmModalModule() { - angular - .module('app/visualize/ConfirmModal', ['react']) - .factory('confirmModal', confirmModalFactory) - .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); -} - function createLocalStateModule() { angular .module('app/visualize/State', [ diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts index 7c17f0c6bddc0..4481bad79c47d 100644 --- a/src/legacy/core_plugins/management/public/legacy.ts +++ b/src/legacy/core_plugins/management/public/legacy.ts @@ -41,5 +41,5 @@ import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, {}); +export const setup = pluginInstance.setup(npSetup.core, { home: npSetup.plugins.home }); export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/legacy/core_plugins/management/public/np_ready/mocks.ts index 13a0cf4c891a3..5ed7c045d1f64 100644 --- a/src/legacy/core_plugins/management/public/np_ready/mocks.ts +++ b/src/legacy/core_plugins/management/public/np_ready/mocks.ts @@ -19,7 +19,12 @@ import { PluginInitializerContext } from 'src/core/public'; import { coreMock } from '../../../../../core/public/mocks'; -import { ManagementSetup, ManagementStart, ManagementPlugin } from './plugin'; +import { + ManagementSetup, + ManagementStart, + ManagementPlugin, + ManagementPluginSetupDependencies, +} from './plugin'; const createSetupContract = (): ManagementSetup => ({ indexPattern: { @@ -49,7 +54,13 @@ const createStartContract = (): ManagementStart => ({}); const createInstance = async () => { const plugin = new ManagementPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup(), {}); + const setup = plugin.setup(coreMock.createSetup(), ({ + home: { + featureCatalogue: { + register: jest.fn(), + }, + }, + } as unknown) as ManagementPluginSetupDependencies); const doStart = () => plugin.start(coreMock.createStart(), {}); return { diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/legacy/core_plugins/management/public/np_ready/plugin.ts index 032a46439ba55..7dd2b23d40610 100644 --- a/src/legacy/core_plugins/management/public/np_ready/plugin.ts +++ b/src/legacy/core_plugins/management/public/np_ready/plugin.ts @@ -17,14 +17,16 @@ * under the License. */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; import { SavedObjectsManagementService, SavedObjectsManagementServiceSetup, } from './services/saved_objects_management'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface ManagementPluginSetupDependencies {} +export interface ManagementPluginSetupDependencies { + home: HomePublicPluginSetup; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface interface ManagementPluginStartDependencies {} @@ -50,10 +52,10 @@ export class ManagementPlugin constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, deps: ManagementPluginSetupDependencies) { + public setup(core: CoreSetup, { home }: ManagementPluginSetupDependencies) { return { - indexPattern: this.indexPattern.setup({ httpClient: core.http }), - savedObjects: this.savedObjects.setup(), + indexPattern: this.indexPattern.setup({ httpClient: core.http, home }), + savedObjects: this.savedObjects.setup({ home }), }; } diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts index b421024b60f4b..2b6f008dd928a 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts @@ -17,12 +17,18 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../../../plugins/home/public'; import { HttpSetup } from '../../../../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; interface SetupDependencies { httpClient: HttpSetup; + home: HomePublicPluginSetup; } /** @@ -31,13 +37,28 @@ interface SetupDependencies { * @internal */ export class IndexPatternManagementService { - public setup({ httpClient }: SetupDependencies) { + public setup({ httpClient, home }: SetupDependencies) { const creation = new IndexPatternCreationManager(httpClient); const list = new IndexPatternListManager(); creation.add(IndexPatternCreationConfig); list.add(IndexPatternListConfig); + home.featureCatalogue.register({ + id: 'index_patterns', + title: i18n.translate('management.indexPatternHeader', { + defaultMessage: 'Index Patterns', + }), + description: i18n.translate('management.indexPatternLabel', { + defaultMessage: + 'Manage the index patterns that help retrieve your data from Elasticsearch.', + }), + icon: 'indexPatternApp', + path: '/app/kibana#/management/kibana/index_patterns', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); + return { creation, list, diff --git a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts index d5e90d12cccc9..be102b2a4dce7 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts @@ -16,10 +16,35 @@ * specific language governing permissions and limitations * under the License. */ + +import { i18n } from '@kbn/i18n'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../../../plugins/home/public'; import { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; +interface SetupDependencies { + home: HomePublicPluginSetup; +} + export class SavedObjectsManagementService { - public setup() { + public setup({ home }: SetupDependencies) { + home.featureCatalogue.register({ + id: 'saved_objects', + title: i18n.translate('management.objects.savedObjectsTitle', { + defaultMessage: 'Saved Objects', + }), + description: i18n.translate('management.objects.savedObjectsDescription', { + defaultMessage: + 'Import, export, and manage your saved searches, visualizations, and dashboards.', + }), + icon: 'savedObjectsApp', + path: '/app/kibana#/management/kibana/objects', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); + return { registry: SavedObjectsManagementActionRegistry, }; diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index a7fa9e0290a1c..ff8f75c23435e 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -114,7 +114,6 @@ app.controller('timelion', function( $timeout, AppState, config, - confirmModal, kbnUrl, Private ) { @@ -230,7 +229,6 @@ app.controller('timelion', function( } const confirmModalOptions = { - onConfirm: doDelete, confirmButtonText: i18n.translate('timelion.topNavMenu.delete.modal.confirmButtonLabel', { defaultMessage: 'Delete', }), @@ -241,12 +239,18 @@ app.controller('timelion', function( }; $scope.$evalAsync(() => { - confirmModal( - i18n.translate('timelion.topNavMenu.delete.modal.warningText', { - defaultMessage: `You can't recover deleted sheets.`, - }), - confirmModalOptions - ); + npStart.core.overlays + .openConfirm( + i18n.translate('timelion.topNavMenu.delete.modal.warningText', { + defaultMessage: `You can't recover deleted sheets.`, + }), + confirmModalOptions + ) + .then(isConfirmed => { + if (isConfirmed) { + doDelete(); + } + }); }); }, testId: 'timelionDeleteButton', diff --git a/src/legacy/core_plugins/timelion/public/register_feature.ts b/src/legacy/core_plugins/timelion/public/register_feature.ts deleted file mode 100644 index 7dd44b58bd1d7..0000000000000 --- a/src/legacy/core_plugins/timelion/public/register_feature.ts +++ /dev/null @@ -1,36 +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 { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { i18n } from '@kbn/i18n'; - -export const registerFeature = () => { - return { - id: 'timelion', - title: 'Timelion', - description: i18n.translate('timelion.registerFeatureDescription', { - defaultMessage: - 'Use an expression language to analyze time series data and visualize the results.', - }), - icon: 'timelionApp', - path: '/app/timelion', - showOnHomePage: false, - category: FeatureCatalogueCategory.DATA, - }; -}; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index 9cad09a2e435d..64abee729f4e7 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -24,7 +24,7 @@ import { isColorDark } from '@elastic/eui'; import { getFormat } from '../legacy_imports'; import { MetricVisValue } from './metric_vis_value'; -import { fieldFormats } from '../../../../../plugins/data/public'; +import { FieldFormatsContentType, IFieldFormat } from '../../../../../plugins/data/public'; import { Context } from '../metric_vis_fn'; import { KibanaDatatable } from '../../../../../plugins/expressions/public'; import { getHeatmapColors } from '../../../../../plugins/charts/public'; @@ -100,9 +100,9 @@ export class MetricVisComponent extends Component { } private getFormattedValue = ( - fieldFormatter: fieldFormats.FieldFormat, + fieldFormatter: IFieldFormat, value: any, - format: fieldFormats.ContentType = 'text' + format: FieldFormatsContentType = 'text' ) => { if (isNaN(value)) return '-'; return fieldFormatter.convert(value, format); @@ -119,7 +119,7 @@ export class MetricVisComponent extends Component { const metrics: MetricVisMetric[] = []; let bucketColumnId: string; - let bucketFormatter: fieldFormats.FieldFormat; + let bucketFormatter: IFieldFormat; if (dimensions.bucket) { bucketColumnId = table.columns[dimensions.bucket.accessor].id; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts index 4f535c62f56a0..28565e0181b84 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -19,6 +19,9 @@ import $ from 'jquery'; +// TODO This is an integration test and thus requires a running platform. When moving to the new platform, +// this test has to be migrated to the newly created integration test environment. +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { npStart } from 'ui/new_platform'; // @ts-ignore import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts index 9f3a8327c9ad9..18d8e7bc9d8bb 100644 --- a/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts +++ b/src/legacy/core_plugins/vis_type_table/public/get_inner_angular.ts @@ -21,7 +21,6 @@ // these are necessary to bootstrap the local angular. // They can stay even after NP cutover import angular from 'angular'; -import 'ui/angular-bootstrap'; import 'angular-recursion'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; @@ -34,6 +33,9 @@ import { StateManagementConfigProvider, configureAppAngularModule, } from './legacy_imports'; +import { initAngularBootstrap } from '../../../../plugins/kibana_legacy/public'; + +initAngularBootstrap(); const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; diff --git a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts index 781782e42fbaf..7352236f03feb 100644 --- a/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/paginated_table/paginated_table.test.ts @@ -22,11 +22,15 @@ import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; import $ from 'jquery'; import 'angular-sanitize'; import 'angular-mocks'; -import '../table_vis.mock'; import { getAngularModule } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; -import { npStart } from '../legacy_imports'; +import { coreMock } from '../../../../../core/public/mocks'; + +jest.mock('ui/new_platform'); +jest.mock('../../../../../plugins/kibana_legacy/public/angular/angular_config', () => ({ + configureAppAngularModule: () => {}, +})); interface Sort { columnIndex: number; @@ -69,7 +73,7 @@ describe('Table Vis - Paginated table', () => { let paginatedTable: any; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + const tableVisModule = getAngularModule('kibana/table_vis', coreMock.createStart()); initTableVisLegacyModule(tableVisModule); }; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts deleted file mode 100644 index d04964cb7af03..0000000000000 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis.mock.ts +++ /dev/null @@ -1,46 +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 { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; -import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { injectedMetadataServiceMock } from '../../../../core/public/mocks'; - -jest.doMock('ui/new_platform', () => { - const npMock = createUiNewPlatformMock(); - return { - npSetup: { - ...npMock.npSetup, - core: { - ...npMock.npSetup.core, - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), - }, - }, - npStart: { - ...npMock.npStart, - core: { - ...npMock.npStart.core, - injectedMetadata: injectedMetadataServiceMock.createStartContract(), - }, - }, - }; -}); - -Object.assign(window, { - sessionStorage: new StubBrowserStorage(), -}); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index d8912975227bf..0e1e48d00a1b2 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -21,21 +21,26 @@ import angular, { IRootScopeService, IScope, ICompileService } from 'angular'; import 'angular-mocks'; import 'angular-sanitize'; import $ from 'jquery'; -import './table_vis.mock'; // @ts-ignore import StubIndexPattern from 'test_utils/stub_index_pattern'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; -import { npStart, IAggConfig, tabifyAggResponse } from './legacy_imports'; import { tableVisTypeDefinition } from './table_vis_type'; import { Vis } from '../../visualizations/public'; -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; // eslint-disable-next-line import { stubFields } from '../../../../plugins/data/public/stubs'; // eslint-disable-next-line -import { setFieldFormats } from '../../../../plugins/data/public/services'; import { tableVisResponseHandler } from './table_vis_response_handler'; +import { coreMock } from '../../../../core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AggConfigs } from 'ui/agg_types'; +import { tabifyAggResponse, IAggConfig } from './legacy_imports'; + +jest.mock('ui/new_platform'); +jest.mock('../../../../plugins/kibana_legacy/public/angular/angular_config', () => ({ + configureAppAngularModule: () => {}, +})); interface TableVisScope extends IScope { [key: string]: any; @@ -79,14 +84,11 @@ describe('Table Vis - Controller', () => { let stubIndexPattern: any; const initLocalAngular = () => { - const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); + const tableVisModule = getAngularModule('kibana/table_vis', coreMock.createStart()); initTableVisLegacyModule(tableVisModule); }; beforeEach(initLocalAngular); - beforeAll(() => { - visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); - }); beforeEach(angular.mock.module('kibana/table_vis')); beforeEach( @@ -98,38 +100,38 @@ describe('Table Vis - Controller', () => { ); beforeEach(() => { - setFieldFormats(({ - getDefaultInstance: jest.fn(), - } as unknown) as any); stubIndexPattern = new StubIndexPattern( 'logstash-*', (cfg: any) => cfg, 'time', stubFields, - npStart.core + coreMock.createStart() ); }); function getRangeVis(params?: object) { - // @ts-ignore - return new Vis(stubIndexPattern, { - type: 'table', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 }, - ], + return ({ + type: tableVisTypeDefinition, + params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params), + aggs: new AggConfigs( + stubIndexPattern, + [ + { type: 'count', schema: 'metric' }, + { + type: 'range', + schema: 'bucket', + params: { + field: 'bytes', + ranges: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + }, }, - }, - ], - }); + ], + tableVisTypeDefinition.editorConfig.schemas.all + ), + } as unknown) as Vis; } const dimensions = { @@ -241,13 +243,13 @@ describe('Table Vis - Controller', () => { const vis = getRangeVis({ showPartialRows: true }); initController(vis); - expect(vis.isHierarchical()).toEqual(true); + expect(vis.type.hierarchicalData(vis)).toEqual(true); }); test('passes partialRows:false to tabify based on the vis params', () => { const vis = getRangeVis({ showPartialRows: false }); initController(vis); - expect(vis.isHierarchical()).toEqual(false); + expect(vis.type.hierarchicalData(vis)).toEqual(false); }); }); diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index eed5ffe8c3584..ab7c2cd980c42 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -21,10 +21,10 @@ import React from 'react'; import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ValidatedDualRange } from 'ui/validated_range'; import { VisOptionsProps } from '../../../vis_default_editor/public'; import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { TagCloudVisParams } from '../types'; +import { ValidatedDualRange } from '../legacy_imports'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { const handleFontSizeChange = ([minFontSize, maxFontSize]: [string | number, string | number]) => { diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js index f2163abbbc723..5528278adf4eb 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js @@ -21,9 +21,9 @@ import React from 'react'; import * as Rx from 'rxjs'; import { take } from 'rxjs/operators'; import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; -import { I18nContext } from 'ui/i18n'; +import { getFormat } from '../legacy_imports'; import { Label } from './label'; import { TagCloud } from './tag_cloud'; @@ -65,9 +65,9 @@ export function createTagCloudVisualization({ colors }) { this._containerNode.appendChild(this._feedbackNode); this._feedbackMessage = React.createRef(); render( - + - , + , this._feedbackNode ); diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy_imports.ts index ecc56ea0c34be..d5b442bc5b346 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy_imports.ts @@ -18,3 +18,5 @@ */ export { Schemas } from 'ui/agg_types'; +export { ValidatedDualRange } from 'ui/validated_range'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js index 61662787c982d..26723da5ab5c9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_default_query_language.js @@ -17,8 +17,8 @@ * under the License. */ -import chrome from 'ui/chrome'; +import { getUISettings } from '../../services'; export function getDefaultQueryLanguage() { - return chrome.getUiSettingsClient().get('search:queryLanguage'); + return getUISettings().get('search:queryLanguage'); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js index 0705805312d2f..3ab8e0f6b885e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.js @@ -19,11 +19,11 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; -import { npStart } from 'ui/new_platform'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; +import { getFieldFormats } from '../../services'; export const createTickFormatter = (format = '0,0.[00]', template, getConfig = null) => { - const fieldFormats = npStart.plugins.data.fieldFormats; + const fieldFormats = getFieldFormats(); if (!template) template = '{{value}}'; const render = handlebars.compile(template, { knownHelpersOnly: true }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js index 76d3cff17343e..e87cba126bb46 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js @@ -17,9 +17,9 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; import { createTickFormatter } from './tick_formatter'; import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; +import { setFieldFormats } from '../../services'; const mockUiSettings = { get: item => { @@ -46,9 +46,7 @@ const mockCore = { }; describe('createTickFormatter(format, template)', () => { - npStart.plugins.data = { - fieldFormats: getFieldFormatsRegistry(mockCore), - }; + setFieldFormats(getFieldFormatsRegistry(mockCore)); test('returns a number with two decimal place by default', () => { const fn = createTickFormatter(); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js index 9ec8184dbaebb..d92dafadb68bc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js @@ -26,6 +26,10 @@ jest.mock('plugins/data', () => { }; }); +jest.mock('../lib/get_default_query_language', () => ({ + getDefaultQueryLanguage: () => 'kuery', +})); + import { GaugePanelConfig } from './gauge'; describe('GaugePanelConfig', () => { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js index 3dedb67bd1d99..b2dd1813e6d20 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js @@ -30,13 +30,11 @@ import { createBrushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns'; import { esKuery } from '../../../../../plugins/data/public'; - -import { npStart } from 'ui/new_platform'; +import { getSavedObjectsClient, getUISettings, getDataStart, getCoreStart } from '../services'; import { CoreStartContextProvider } from '../contexts/query_input_bar_context'; import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { timefilter } from 'ui/timefilter'; const VIS_STATE_DEBOUNCE_DELAY = 200; const APP_NAME = 'VisEditor'; @@ -52,7 +50,7 @@ export class VisEditor extends Component { visFields: props.visFields, extractedIndexPatterns: [''], }; - this.onBrush = createBrushHandler(timefilter); + this.onBrush = createBrushHandler(getDataStart().query.timefilter); this.visDataSubject = new Rx.BehaviorSubject(this.props.visData); this.visData$ = this.visDataSubject.asObservable().pipe(share()); @@ -60,8 +58,8 @@ export class VisEditor extends Component { // core dependencies required by React components downstream. this.coreContext = { appName: APP_NAME, - uiSettings: npStart.core.uiSettings, - savedObjectsClient: npStart.core.savedObjects.client, + uiSettings: getUISettings(), + savedObjectsClient: getSavedObjectsClient(), store: this.localStorage, }; } @@ -175,8 +173,8 @@ export class VisEditor extends Component { services={{ appName: APP_NAME, storage: this.localStorage, - data: npStart.plugins.data, - ...npStart.core, + data: getDataStart(), + ...getCoreStart(), }} >
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js index 94f4506cd0172..1fe9358cbfea9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/vis.js @@ -20,7 +20,6 @@ import _, { isArray, last, get } from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { npStart } from 'ui/new_platform'; import { createTickFormatter } from '../../lib/tick_formatter'; import { calculateLabel } from '../../../../../../../plugins/vis_type_timeseries/common/calculate_label'; import { isSortable } from './is_sortable'; @@ -28,6 +27,7 @@ import { EuiToolTip, EuiIcon } from '@elastic/eui'; import { replaceVars } from '../../lib/replace_vars'; import { fieldFormats } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getFieldFormats } from '../../../services'; import { METRIC_TYPES } from '../../../../../../../plugins/vis_type_timeseries/common/metric_types'; @@ -49,7 +49,7 @@ export class TableVis extends Component { constructor(props) { super(props); - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const DateFormat = fieldFormatsService.getType(fieldFormats.FIELD_FORMAT_IDS.DATE); this.dateFormatter = new DateFormat({}, this.props.getConfig); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js index 5243f5f92a621..954d3d174bb8c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js @@ -22,7 +22,6 @@ import React, { Component } from 'react'; import reactCSS from 'reactcss'; import { startsWith, get, cloneDeep, map } from 'lodash'; -import { toastNotifications } from 'ui/notify'; import { htmlIdGenerator } from '@elastic/eui'; import { ScaleType } from '@elastic/charts'; @@ -36,6 +35,7 @@ import { areFieldsDifferent } from '../../lib/charts'; import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; import { isBackgroundDark } from '../../../lib/set_is_reversed'; import { STACKED_OPTIONS } from '../../../visualizations/constants'; +import { getCoreStart } from '../../../services'; export class TimeseriesVisualization extends Component { static propTypes = { @@ -108,6 +108,7 @@ export class TimeseriesVisualization extends Component { createTickFormatter(get(model, 'formatter'), get(model, 'value_template'), getConfig); componentDidUpdate() { + const toastNotifications = getCoreStart().notifications.toasts; if ( this.showToastNotification && this.notificationReason !== this.showToastNotification.reason diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts index 93b35ee284f18..fb22bbd4146e2 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts @@ -32,4 +32,4 @@ const plugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/ui/public/modals/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts similarity index 79% rename from src/legacy/ui/public/modals/index.js rename to src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts index d061264345959..401acfc8df766 100644 --- a/src/legacy/ui/public/modals/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts @@ -17,8 +17,8 @@ * under the License. */ -import './confirm_modal'; -import './confirm_modal_promise'; - -export { ConfirmationButtonTypes } from './confirm_modal'; -export { ModalOverlay } from './modal_overlay'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js b/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js index 68e694f23fa7f..9c64d0da2d88a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/lib/fetch_fields.js @@ -16,19 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { kfetch } from 'ui/kfetch'; -import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import { extractIndexPatterns } from '../../../../../plugins/vis_type_timeseries/common/extract_index_patterns'; +import { getCoreStart } from '../services'; export async function fetchFields(indexPatterns = ['*']) { const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns]; try { const indexFields = await Promise.all( patterns.map(pattern => { - return kfetch({ - method: 'GET', - pathname: '/api/metrics/fields', + return getCoreStart().http.get('/api/metrics/fields', { query: { index: pattern, }, @@ -43,7 +40,7 @@ export async function fetchFields(indexPatterns = ['*']) { }, {}); return fields; } catch (error) { - toastNotifications.addDanger({ + getCoreStart().notifications.toasts.addDanger({ title: i18n.translate('visTypeTimeseries.fetchFields.loadIndexPatternFieldsErrorMessage', { defaultMessage: 'Unable to load index_pattern fields', }), diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts index 225d81b71b8e0..5786399fc7830 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts @@ -19,11 +19,11 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { PersistedState } from 'ui/persisted_state'; import { ExpressionFunction, KibanaContext, Render } from '../../../../plugins/expressions/public'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; +import { PersistedState } from './legacy_imports'; const name = 'tsvb'; type Context = KibanaContext | null; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts index 22d2b3b10e566..01750ee0c448d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts @@ -18,8 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +import { defaultFeedbackMessage } from './legacy_imports'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 4d1222d6f5a87..38a9c68487854 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -16,29 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - SavedObjectsClientContract, - IUiSettingsClient, -} from '../../../../core/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { metricsVisDefinition } from './metrics_type'; -import { setSavedObjectsClient, setUISettings, setI18n } from './services'; +import { + setSavedObjectsClient, + setUISettings, + setI18n, + setFieldFormats, + setCoreStart, + setDataStart, +} from './services'; +import { DataPublicPluginStart } from '../../../../plugins/data/public'; /** @internal */ export interface MetricsPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; } -export interface MetricsVisualizationDependencies { - uiSettings: IUiSettingsClient; - savedObjectsClient: SavedObjectsClientContract; + +/** @internal */ +export interface MetricsPluginStartDependencies { + data: DataPublicPluginStart; } /** @internal */ @@ -58,9 +60,11 @@ export class MetricsPlugin implements Plugin, void> { visualizations.types.createReactVisualization(metricsVisDefinition); } - public start(core: CoreStart) { - // nothing to do here yet + public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { setSavedObjectsClient(core.savedObjects); setI18n(core.i18n); + setFieldFormats(data.fieldFormats); + setDataStart(data); + setCoreStart(core); } } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js index 84f62612aa974..032ef335314d9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js @@ -18,10 +18,8 @@ */ import { validateInterval } from './lib/validate_interval'; -import { timezoneProvider } from 'ui/vis/lib/timezone'; -import { timefilter } from 'ui/timefilter'; -import { kfetch } from 'ui/kfetch'; -import { getUISettings } from './services'; +import { timezoneProvider } from './legacy_imports'; +import { getUISettings, getDataStart, getCoreStart } from './services'; export const metricsRequestHandler = async ({ uiState, @@ -34,7 +32,7 @@ export const metricsRequestHandler = async ({ const config = getUISettings(); const timezone = timezoneProvider(config)(); const uiStateObj = uiState.get(visParams.type, {}); - const parsedTimeRange = timefilter.calculateBounds(timeRange); + const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange); const scaledDataFormat = config.get('dateFormat:scaled'); const dateFormat = config.get('dateFormat'); @@ -44,9 +42,7 @@ export const metricsRequestHandler = async ({ validateInterval(parsedTimeRange, visParams, maxBuckets); - const resp = await kfetch({ - pathname: '/api/metrics/vis/data', - method: 'POST', + const resp = await getCoreStart().http.post('/api/metrics/vis/data', { body: JSON.stringify({ timerange: { timezone, diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts index af04578b8e27f..64ee897247b89 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/services.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts @@ -17,13 +17,22 @@ * under the License. */ -import { I18nStart, SavedObjectsStart, IUiSettingsClient } from 'src/core/public'; +import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public'; import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; +import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); +export const [getFieldFormats, setFieldFormats] = createGetterSetter< + DataPublicPluginStart['fieldFormats'] +>('FieldFormats'); + export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter( 'SavedObjectsClient' ); +export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); + +export const [getDataStart, setDataStart] = createGetterSetter('DataStart'); + export const [getI18n, setI18n] = createGetterSetter('I18n'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js index bcd0b6314cef1..986111b462b35 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js @@ -33,9 +33,9 @@ import { } from '@elastic/charts'; import { EuiIcon } from '@elastic/eui'; -import { timezoneProvider } from 'ui/vis/lib/timezone'; +import { timezoneProvider } from '../../../legacy_imports'; import { eventBus, ACTIVE_CURSOR } from '../../lib/active_cursor'; -import chrome from 'ui/chrome'; +import { getUISettings } from '../../../services'; import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants'; import { AreaSeriesDecorator } from './decorators/area_decorator'; import { BarSeriesDecorator } from './decorators/bar_decorator'; @@ -85,7 +85,7 @@ export const TimeSeries = ({ }, []); // eslint-disable-line const tooltipFormatter = decorateFormatter(xAxisFormatter); - const uiSettings = chrome.getUiSettingsClient(); + const uiSettings = getUISettings(); const timeZone = timezoneProvider(uiSettings)(); const hasBarChart = series.some(({ bars }) => bars.show); diff --git a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts new file mode 100644 index 0000000000000..64a9aaaf3b7a6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts @@ -0,0 +1,55 @@ +/* + * 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 { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; +import { IUiSettingsClient, NotificationsStart, SavedObjectsStart } from 'kibana/public'; +import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; + +export const [getData, setData] = createGetterSetter('Data'); +setData(dataPluginMock.createStartContract()); + +export const [getNotifications, setNotifications] = createGetterSetter( + 'Notifications' +); +setNotifications(coreMock.createStart().notifications); + +export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); +setUISettings(coreMock.createStart().uiSettings); + +export const [getSavedObjects, setSavedObjects] = createGetterSetter( + 'SavedObjects' +); +setSavedObjects(coreMock.createStart().savedObjects); + +export const [getInjectedVars, setInjectedVars] = createGetterSetter<{ + esShardTimeout: number; + enableExternalUrls: boolean; + emsTileLayerId: unknown; +}>('InjectedVars'); +setInjectedVars({ + emsTileLayerId: {}, + enableExternalUrls: true, + esShardTimeout: 10000, +}); + +export const getEsShardTimeout = () => getInjectedVars().esShardTimeout; +export const getEnableExternalUrls = () => getInjectedVars().enableExternalUrls; +export const getEmsTileLayerId = () => getInjectedVars().emsTileLayerId; diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 6c9eb86a9d2c0..b2ad45b5d7b6d 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -43,6 +43,9 @@ import { SearchCache } from '../data_model/search_cache'; import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; import { createVegaTypeDefinition } from '../vega_type'; +// TODO This is an integration test and thus requires a running platform. When moving to the new platform, +// this test has to be migrated to the newly created integration test environment. +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { npStart } from 'ui/new_platform'; const THRESHOLD = 0.1; diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx b/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx index 18d48aea5d39a..707a6830b5ba4 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx +++ b/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx @@ -24,7 +24,7 @@ import compactStringify from 'json-stringify-pretty-compact'; import hjson from 'hjson'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; +import { getNotifications } from '../services'; import { VisParams } from '../vega_fn'; import { VegaHelpMenu } from './vega_help_menu'; import { VegaActionsMenu } from './vega_actions_menu'; @@ -50,7 +50,7 @@ function format(value: string, stringify: typeof compactStringify, options?: any return stringify(spec, options); } catch (err) { // This is a common case - user tries to format an invalid HJSON text - toastNotifications.addError(err, { + getNotifications().toasts.addError(err, { title: i18n.translate('visTypeVega.editor.formatError', { defaultMessage: 'Error formatting spec', }), diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js index 2f25f70610a81..7c239800483f0 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { getEsShardTimeout } from '../helpers'; +import { getEsShardTimeout } from '../services'; const TIMEFILTER = '%timefilter%'; const AUTOINTERVAL = '%autointerval%'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js index 691e5e8944241..c519da33ab1c9 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js @@ -21,10 +21,6 @@ import { cloneDeep } from 'lodash'; import moment from 'moment'; import { EsQueryParser } from './es_query_parser'; -jest.mock('../helpers', () => ({ - getEsShardTimeout: jest.fn(() => '10000'), -})); - const second = 1000; const minute = 60 * second; const hour = 60 * minute; @@ -47,6 +43,8 @@ function create(min, max, dashboardCtx) { return inst; } +jest.mock('../services'); + describe(`EsQueryParser time`, () => { test(`roundInterval(4s)`, () => { expect(EsQueryParser._roundInterval(4 * second)).toBe(`1s`); diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js index 0ec018f46c02b..92f80545ce1b5 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js @@ -18,6 +18,7 @@ */ import { SearchCache } from './search_cache'; +jest.mock('../services'); describe(`SearchCache`, () => { class FauxEs { diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js index b76709ea2c934..074744a0bda5e 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js @@ -18,6 +18,7 @@ */ import { TimeCache } from './time_cache'; +jest.mock('../services'); describe(`TimeCache`, () => { class FauxTimefilter { diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js index 1bc8b1f90daab..78d1cad874311 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js @@ -20,6 +20,7 @@ import { cloneDeep } from 'lodash'; import { VegaParser } from './vega_parser'; import { bypassExternalUrlCheck } from '../vega_view/vega_base_view'; +jest.mock('../services'); describe(`VegaParser._setDefaultValue`, () => { function check(spec, expected, ...params) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/helpers/index.js b/src/legacy/core_plugins/vis_type_vega/public/helpers/index.js deleted file mode 100644 index e9d6eb21fd3c7..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/helpers/index.js +++ /dev/null @@ -1,20 +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. - */ - -export * from './vega_config_provider'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/helpers/vega_config_provider.js b/src/legacy/core_plugins/vis_type_vega/public/helpers/vega_config_provider.js deleted file mode 100644 index cc41b18479f93..0000000000000 --- a/src/legacy/core_plugins/vis_type_vega/public/helpers/vega_config_provider.js +++ /dev/null @@ -1,23 +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 chrome from 'ui/chrome'; - -export const getEsShardTimeout = () => chrome.getInjected('esShardTimeout'); -export const getEnableExternalUrls = () => chrome.getInjected('enableExternalUrls'); diff --git a/src/plugins/data/public/autocomplete/static.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts similarity index 77% rename from src/plugins/data/public/autocomplete/static.ts rename to src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts index 7d627486c6d65..9e1067ed9099a 100644 --- a/src/plugins/data/public/autocomplete/static.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/legacy_imports.ts @@ -17,11 +17,9 @@ * under the License. */ -export { - QuerySuggestion, - QuerySuggestionsTypes, - QuerySuggestionsGetFn, - QuerySuggestionsGetFnArgs, - BasicQuerySuggestion, - FieldQuerySuggestion, -} from './providers/query_suggestion_provider'; +// @ts-ignore +export { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +// @ts-ignore +export { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; +// @ts-ignore +export { KibanaMap } from 'ui/vis/map/kibana_map'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index 75444a4a4f8e4..9721de9848cfc 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -21,7 +21,13 @@ import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim' import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public'; -import { setNotifications, setData, setSavedObjects } from './services'; +import { + setNotifications, + setData, + setSavedObjects, + setInjectedVars, + setUISettings, +} from './services'; import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; @@ -59,6 +65,13 @@ export class VegaPlugin implements Plugin, void> { core: CoreSetup, { data, expressions, visualizations, __LEGACY }: VegaPluginSetupDependencies ) { + setInjectedVars({ + esShardTimeout: core.injectedMetadata.getInjectedVar('esShardTimeout') as number, + enableExternalUrls: core.injectedMetadata.getInjectedVar('enableExternalUrls') as boolean, + emsTileLayerId: core.injectedMetadata.getInjectedVar('emsTileLayerId', true), + }); + setUISettings(core.uiSettings); + const visualizationDependencies: Readonly = { core, plugins: { diff --git a/src/legacy/core_plugins/vis_type_vega/public/services.ts b/src/legacy/core_plugins/vis_type_vega/public/services.ts index 94723f1a378d2..88e0e0098bf8c 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/services.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/services.ts @@ -21,6 +21,7 @@ import { SavedObjectsStart } from 'kibana/public'; import { NotificationsStart } from 'src/core/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; +import { IUiSettingsClient } from '../../../../core/public'; export const [getData, setData] = createGetterSetter('Data'); @@ -28,6 +29,18 @@ export const [getNotifications, setNotifications] = createGetterSetter('UISettings'); + export const [getSavedObjects, setSavedObjects] = createGetterSetter( 'SavedObjects' ); + +export const [getInjectedVars, setInjectedVars] = createGetterSetter<{ + esShardTimeout: number; + enableExternalUrls: boolean; + emsTileLayerId: unknown; +}>('InjectedVars'); + +export const getEsShardTimeout = () => getInjectedVars().esShardTimeout; +export const getEnableExternalUrls = () => getInjectedVars().enableExternalUrls; +export const getEmsTileLayerId = () => getInjectedVars().emsTileLayerId; diff --git a/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts index 5cf65d62a6aed..8925f76cffa43 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/shim/legacy_dependencies_plugin.ts @@ -17,7 +17,10 @@ * under the License. */ +// TODO remove this file as soon as serviceSettings is exposed in the new platform +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import chrome from 'ui/chrome'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import 'ui/vis/map/service_settings'; import { CoreStart, Plugin } from 'kibana/public'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index a7ca0dd3bb349..1d4655b4d525f 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +import { defaultFeedbackMessage } from './legacy_imports'; import { Status } from '../../visualizations/public'; import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js index 9d6adfd11aedd..a6c17547d058e 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -17,7 +17,6 @@ * under the License. */ -import chrome from 'ui/chrome'; import $ from 'jquery'; import moment from 'moment'; import dateMath from '@elastic/datemath'; @@ -29,7 +28,7 @@ import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; import { esFilters } from '../../../../../plugins/data/public'; -import { getEnableExternalUrls } from '../helpers/vega_config_provider'; +import { getEnableExternalUrls } from '../services'; vega.scheme('elastic', VISUALIZATION_COLORS); @@ -279,20 +278,14 @@ export class VegaBaseView { * @param {string} [index] as defined in Kibana, or default if missing */ async removeFilterHandler(query, index) { - const $injector = await chrome.dangerouslyGetActiveInjector(); const indexId = await this._findIndex(index); const filter = esFilters.buildQueryFilter(query, indexId); - // This is a workaround for the https://github.com/elastic/kibana/issues/18863 - // Once fixed, replace with a direct call (no await is needed because its not async) - // this._queryfilter.removeFilter(filter); - $injector.get('$rootScope').$evalAsync(() => { - try { - this._filterManager.removeFilter(filter); - } catch (err) { - this.onError(err); - } - }); + try { + this._filterManager.removeFilter(filter); + } catch (err) { + this.onError(err); + } } removeAllFiltersHandler() { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js index 2794de6946ba0..38540e9f218fb 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_layer.js @@ -19,7 +19,7 @@ import L from 'leaflet'; import 'leaflet-vega'; -import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; +import { KibanaMapLayer } from '../legacy_imports'; export class VegaMapLayer extends KibanaMapLayer { constructor(spec, options) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js index 82bcd6626789f..487c90d01ada3 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_map_view.js @@ -17,12 +17,12 @@ * under the License. */ -import { KibanaMap } from 'ui/vis/map/kibana_map'; import * as vega from 'vega-lib'; +import { i18n } from '@kbn/i18n'; import { VegaBaseView } from './vega_base_view'; import { VegaMapLayer } from './vega_map_layer'; -import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; +import { KibanaMap } from '../legacy_imports'; +import { getEmsTileLayerId, getUISettings } from '../services'; export class VegaMapView extends VegaBaseView { async _initViewCustomizations() { @@ -35,10 +35,10 @@ export class VegaMapView extends VegaBaseView { const tmsServices = await this._serviceSettings.getTMSServices(); // In some cases, Vega may be initialized twice, e.g. after awaiting... if (!this._$container) return; - const emsTileLayerId = chrome.getInjected('emsTileLayerId', true); + const emsTileLayerId = getEmsTileLayerId(); const mapStyle = mapConfig.mapStyle === 'default' ? emsTileLayerId.bright : mapConfig.mapStyle; - const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode'); + const isDarkMode = getUISettings().get('theme:darkMode'); baseMapOpts = tmsServices.find(s => s.id === mapStyle); baseMapOpts = { ...baseMapOpts, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js index 642a032d8b9c2..3574fb232883d 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js @@ -21,6 +21,7 @@ import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { aggResponseIndex } from 'ui/agg_response'; import { vislibSeriesResponseHandler } from '../response_handler'; diff --git a/src/legacy/server/logging/log_reporter.js b/src/legacy/server/logging/log_reporter.js index 78176e94fd126..b64f08c1cbbb6 100644 --- a/src/legacy/server/logging/log_reporter.js +++ b/src/legacy/server/logging/log_reporter.js @@ -24,6 +24,14 @@ import LogFormatJson from './log_format_json'; import LogFormatString from './log_format_string'; import { LogInterceptor } from './log_interceptor'; +// NOTE: legacy logger creates a new stream for each new access +// In https://github.com/elastic/kibana/pull/55937 we reach the max listeners +// default limit of 10 for process.stdout which starts a long warning/error +// thrown every time we start the server. +// In order to keep using the legacy logger until we remove it I'm just adding +// a new hard limit here. +process.stdout.setMaxListeners(15); + export function getLoggerStream({ events, config }) { const squeeze = new Squeeze(events); const format = config.json ? new LogFormatJson(config) : new LogFormatString(config); diff --git a/src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js b/src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js deleted file mode 100755 index bafc738268626..0000000000000 --- a/src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js +++ /dev/null @@ -1,10 +0,0 @@ -angular.module('ui.bootstrap.bindHtml', []) - - .directive('bindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); - scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); - }; - }); diff --git a/src/legacy/ui/public/angular-bootstrap/index.js b/src/legacy/ui/public/angular-bootstrap/index.js deleted file mode 100644 index f574345af48ab..0000000000000 --- a/src/legacy/ui/public/angular-bootstrap/index.js +++ /dev/null @@ -1,47 +0,0 @@ - -/* eslint-disable */ - -/** - * TODO: Write custom components that address our needs to directly and deprecate these Bootstrap components. - */ - -import 'angular'; - -import { uiModules } from 'ui/modules'; - -uiModules.get('kibana', [ - 'ui.bootstrap', -]); - -/* - * angular-ui-bootstrap - * http://angular-ui.github.io/bootstrap/ - - * Version: 0.12.1 - 2015-02-20 - * License: MIT - */ -angular.module('ui.bootstrap', [ - 'ui.bootstrap.tpls', - 'ui.bootstrap.bindHtml', - 'ui.bootstrap.tooltip', -]); - -angular.module('ui.bootstrap.tpls', [ - 'template/tooltip/tooltip-html-unsafe-popup.html', - 'template/tooltip/tooltip-popup.html', -]); - -import './bindHtml/bindHtml'; -import './tooltip/tooltip'; - -import tooltipUnsafePopup from './tooltip/tooltip-html-unsafe-popup.html'; - -angular.module('template/tooltip/tooltip-html-unsafe-popup.html', []).run(['$templateCache', function($templateCache) { - $templateCache.put('template/tooltip/tooltip-html-unsafe-popup.html', tooltipUnsafePopup); -}]); - -import tooltipPopup from './tooltip/tooltip-popup.html'; - -angular.module('template/tooltip/tooltip-popup.html', []).run(['$templateCache', function($templateCache) { - $templateCache.put('template/tooltip/tooltip-popup.html', tooltipPopup); -}]); diff --git a/src/legacy/ui/public/angular-bootstrap/tooltip/position.js b/src/legacy/ui/public/angular-bootstrap/tooltip/position.js deleted file mode 100755 index 3444c33449152..0000000000000 --- a/src/legacy/ui/public/angular-bootstrap/tooltip/position.js +++ /dev/null @@ -1,152 +0,0 @@ -angular.module('ui.bootstrap.position', []) - -/** - * A set of utility methods that can be use to retrieve position of DOM elements. - * It is meant to be used where we need to absolute-position DOM elements in - * relation to other, existing elements (this is the case for tooltips, popovers, - * typeahead suggestions etc.). - */ - .factory('$position', ['$document', '$window', function ($document, $window) { - - function getStyle(el, cssprop) { - if (el.currentStyle) { //IE - return el.currentStyle[cssprop]; - } else if ($window.getComputedStyle) { - return $window.getComputedStyle(el)[cssprop]; - } - // finally try and get inline style - return el.style[cssprop]; - } - - /** - * Checks if a given element is statically positioned - * @param element - raw DOM element - */ - function isStaticPositioned(element) { - return (getStyle(element, 'position') || 'static' ) === 'static'; - } - - /** - * returns the closest, non-statically positioned parentOffset of a given element - * @param element - */ - var parentOffsetEl = function (element) { - var docDomEl = $document[0]; - var offsetParent = element.offsetParent || docDomEl; - while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent || docDomEl; - }; - - return { - /** - * Provides read-only equivalent of jQuery's position function: - * http://api.jquery.com/position/ - */ - position: function (element) { - var elBCR = this.offset(element); - var offsetParentBCR = { top: 0, left: 0 }; - var offsetParentEl = parentOffsetEl(element[0]); - if (offsetParentEl != $document[0]) { - offsetParentBCR = this.offset(angular.element(offsetParentEl)); - offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; - offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; - } - - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: elBCR.top - offsetParentBCR.top, - left: elBCR.left - offsetParentBCR.left - }; - }, - - /** - * Provides read-only equivalent of jQuery's offset function: - * http://api.jquery.com/offset/ - */ - offset: function (element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) - }; - }, - - /** - * Provides coordinates for the targetEl in relation to hostEl - */ - positionElements: function (hostEl, targetEl, positionStr, appendToBody) { - - var positionStrParts = positionStr.split('-'); - var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; - - var hostElPos, - targetElWidth, - targetElHeight, - targetElPos; - - hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); - - targetElWidth = targetEl.prop('offsetWidth'); - targetElHeight = targetEl.prop('offsetHeight'); - - var shiftWidth = { - center: function () { - return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; - }, - left: function () { - return hostElPos.left; - }, - right: function () { - return hostElPos.left + hostElPos.width; - } - }; - - var shiftHeight = { - center: function () { - return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; - }, - top: function () { - return hostElPos.top; - }, - bottom: function () { - return hostElPos.top + hostElPos.height; - } - }; - - switch (pos0) { - case 'right': - targetElPos = { - top: shiftHeight[pos1](), - left: shiftWidth[pos0]() - }; - break; - case 'left': - targetElPos = { - top: shiftHeight[pos1](), - left: hostElPos.left - targetElWidth - }; - break; - case 'bottom': - targetElPos = { - top: shiftHeight[pos0](), - left: shiftWidth[pos1]() - }; - break; - default: - targetElPos = { - top: hostElPos.top - targetElHeight, - left: shiftWidth[pos1]() - }; - break; - } - - return targetElPos; - } - }; - }]); diff --git a/src/legacy/ui/public/angular-bootstrap/tooltip/tooltip.js b/src/legacy/ui/public/angular-bootstrap/tooltip/tooltip.js deleted file mode 100755 index b59b2922d8089..0000000000000 --- a/src/legacy/ui/public/angular-bootstrap/tooltip/tooltip.js +++ /dev/null @@ -1,374 +0,0 @@ -import './position'; - -/** - * The following features are still outstanding: animation as a - * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html tooltips, and selector delegation. - */ -angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) - -/** - * The $tooltip service creates tooltip- and popover-like directives as well as - * houses global options for them. - */ -.provider( '$tooltip', function () { - // The default options tooltip and popover. - var defaultOptions = { - placement: 'top', - animation: true, - popupDelay: 0 - }; - - // Default hide triggers for each show trigger - var triggerMap = { - 'mouseenter': 'mouseleave', - 'click': 'click', - 'focus': 'blur' - }; - - // The options specified to the provider globally. - var globalOptions = {}; - - /** - * `options({})` allows global configuration of all tooltips in the - * application. - * - * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { - * // place tooltips left instead of top by default - * $tooltipProvider.options( { placement: 'left' } ); - * }); - */ - this.options = function( value ) { - angular.extend( globalOptions, value ); - }; - - /** - * This allows you to extend the set of trigger mappings available. E.g.: - * - * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); - */ - this.setTriggers = function setTriggers ( triggers ) { - angular.extend( triggerMap, triggers ); - }; - - /** - * This is a helper function for translating camel-case to snake-case. - */ - function snake_case(name){ - var regexp = /[A-Z]/g; - var separator = '-'; - return name.replace(regexp, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); - } - - /** - * Returns the actual instance of the $tooltip service. - * TODO support multiple triggers - */ - this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) { - return function $tooltip ( type, prefix, defaultTriggerShow ) { - var options = angular.extend( {}, defaultOptions, globalOptions ); - - /** - * Returns an object of show and hide triggers. - * - * If a trigger is supplied, - * it is used to show the tooltip; otherwise, it will use the `trigger` - * option passed to the `$tooltipProvider.options` method; else it will - * default to the trigger supplied to this directive factory. - * - * The hide trigger is based on the show trigger. If the `trigger` option - * was passed to the `$tooltipProvider.options` method, it will use the - * mapped trigger from `triggerMap` or the passed trigger if the map is - * undefined; otherwise, it uses the `triggerMap` value of the show - * trigger; else it will just use the show trigger. - */ - function getTriggers ( trigger ) { - var show = trigger || options.trigger || defaultTriggerShow; - var hide = triggerMap[show] || show; - return { - show: show, - hide: hide - }; - } - - var directiveName = snake_case( type ); - - var startSym = $interpolate.startSymbol(); - var endSym = $interpolate.endSymbol(); - var template = - '
'+ - '
'; - - return { - restrict: 'EA', - compile: function (tElem, tAttrs) { - var tooltipLinker = $compile( template ); - - return function link ( scope, element, attrs ) { - var tooltip; - var tooltipLinkedScope; - var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); - var ttScope = scope.$new(true); - - var positionTooltip = function () { - - var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); - ttPosition.top += 'px'; - ttPosition.left += 'px'; - - // Now set the calculated positioning. - tooltip.css( ttPosition ); - }; - - // By default, the tooltip is not open. - // TODO add ability to start tooltip opened - ttScope.isOpen = false; - - function toggleTooltipBind () { - if ( ! ttScope.isOpen ) { - showTooltipBind(); - } else { - hideTooltipBind(); - } - } - - // Show the tooltip with delay if specified, otherwise show it immediately - function showTooltipBind() { - if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { - return; - } - - prepareTooltip(); - - if ( ttScope.popupDelay ) { - // Do nothing if the tooltip was already scheduled to pop-up. - // This happens if show is triggered multiple times before any hide is triggered. - if (!popupTimeout) { - popupTimeout = $timeout( show, ttScope.popupDelay, false ); - popupTimeout - .then(reposition => reposition()) - .catch((error) => { - // if the timeout is canceled then the string `canceled` is thrown. To prevent - // this from triggering an 'unhandled promise rejection' in angular 1.5+ the - // $timeout service explicitly tells $q that the promise it generated is "handled" - // but that does not include down chain promises like the one created by calling - // `popupTimeout.then()`. Because of this we need to ignore the "canceled" string - // and only propagate real errors - if (error !== 'canceled') { - throw error - } - }); - } - } else { - show()(); - } - } - - function hideTooltipBind () { - scope.$evalAsync(function () { - hide(); - }); - } - - // Show the tooltip popup element. - function show() { - - popupTimeout = null; - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - transitionTimeout = null; - } - - // Don't show empty tooltips. - if ( ! ttScope.content ) { - return angular.noop; - } - - createTooltip(); - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - ttScope.$digest(); - - positionTooltip(); - - // And show the tooltip. - ttScope.isOpen = true; - ttScope.$digest(); // digest required as $apply is not called - - // Return positioning function as promise callback for correct - // positioning after draw. - return positionTooltip; - } - - // Hide the tooltip popup element. - function hide() { - // First things first: we don't show it anymore. - ttScope.isOpen = false; - - //if tooltip is going to be shown after delay, we must cancel this - $timeout.cancel( popupTimeout ); - popupTimeout = null; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( ttScope.animation ) { - if (!transitionTimeout) { - transitionTimeout = $timeout(removeTooltip, 500); - } - } else { - removeTooltip(); - } - } - - function createTooltip() { - // There can only be one tooltip element per directive shown at once. - if (tooltip) { - removeTooltip(); - } - tooltipLinkedScope = ttScope.$new(); - tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) { - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); - } else { - element.after( tooltip ); - } - }); - } - - function removeTooltip() { - transitionTimeout = null; - if (tooltip) { - tooltip.remove(); - tooltip = null; - } - if (tooltipLinkedScope) { - tooltipLinkedScope.$destroy(); - tooltipLinkedScope = null; - } - } - - function prepareTooltip() { - prepPlacement(); - prepPopupDelay(); - } - - /** - * Observe the relevant attributes. - */ - attrs.$observe( type, function ( val ) { - ttScope.content = val; - - if (!val && ttScope.isOpen ) { - hide(); - } - }); - - attrs.$observe( prefix+'Title', function ( val ) { - ttScope.title = val; - }); - - function prepPlacement() { - var val = attrs[ prefix + 'Placement' ]; - ttScope.placement = angular.isDefined( val ) ? val : options.placement; - } - - function prepPopupDelay() { - var val = attrs[ prefix + 'PopupDelay' ]; - var delay = parseInt( val, 10 ); - ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay; - } - - var unregisterTriggers = function () { - element.unbind(triggers.show, showTooltipBind); - element.unbind(triggers.hide, hideTooltipBind); - }; - - function prepTriggers() { - var val = attrs[ prefix + 'Trigger' ]; - unregisterTriggers(); - - triggers = getTriggers( val ); - - if ( triggers.show === triggers.hide ) { - element.bind( triggers.show, toggleTooltipBind ); - } else { - element.bind( triggers.show, showTooltipBind ); - element.bind( triggers.hide, hideTooltipBind ); - } - } - prepTriggers(); - - var animation = scope.$eval(attrs[prefix + 'Animation']); - ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; - - var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); - appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; - - // if a tooltip is attached to we need to remove it on - // location change as its parent scope will probably not be destroyed - // by the change. - if ( appendToBody ) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( ttScope.isOpen ) { - hide(); - } - }); - } - - // Make sure tooltip is destroyed and removed. - scope.$on('$destroy', function onDestroyTooltip() { - $timeout.cancel( transitionTimeout ); - $timeout.cancel( popupTimeout ); - unregisterTriggers(); - removeTooltip(); - ttScope = null; - }); - }; - } - }; - }; - }]; -}) - -.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); -}]) - -.directive( 'tooltipPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' - }; -}) - -.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); -}]) - -.directive( 'tooltipHtmlUnsafePopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' - }; -}); \ No newline at end of file diff --git a/src/legacy/ui/public/autoload/modules.js b/src/legacy/ui/public/autoload/modules.js index 938796ed279ea..b40f051a5ec10 100644 --- a/src/legacy/ui/public/autoload/modules.js +++ b/src/legacy/ui/public/autoload/modules.js @@ -23,7 +23,6 @@ import '../config'; import '../notify'; import '../private'; import '../promises'; -import '../modals'; import '../state_management/app_state'; import '../state_management/global_state'; import '../style_compile'; diff --git a/src/legacy/ui/public/modals/__tests__/confirm_modal.js b/src/legacy/ui/public/modals/__tests__/confirm_modal.js deleted file mode 100644 index 6c05fb977c701..0000000000000 --- a/src/legacy/ui/public/modals/__tests__/confirm_modal.js +++ /dev/null @@ -1,137 +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 angular from 'angular'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import _ from 'lodash'; - -describe('ui/modals/confirm_modal', function() { - let confirmModal; - let $rootScope; - - beforeEach(function() { - ngMock.module('kibana'); - ngMock.inject(function($injector) { - confirmModal = $injector.get('confirmModal'); - $rootScope = $injector.get('$rootScope'); - }); - }); - - function findByDataTestSubj(dataTestSubj) { - return angular.element(document.body).find(`[data-test-subj=${dataTestSubj}]`); - } - - afterEach(function() { - const confirmButton = findByDataTestSubj('confirmModalConfirmButton'); - if (confirmButton) { - angular.element(confirmButton).click(); - } - }); - - describe('throws an exception', function() { - it('when no custom confirm button passed', function() { - expect(() => confirmModal('hi', { onConfirm: _.noop })).to.throwError(); - }); - - it('when no custom noConfirm function is passed', function() { - expect(() => confirmModal('hi', { confirmButtonText: 'bye' })).to.throwError(); - }); - - it('when showClose is on but title is not given', function() { - const options = { customConfirmButton: 'b', onConfirm: _.noop, showClose: true }; - expect(() => confirmModal('hi', options)).to.throwError(); - }); - }); - - it('shows the message', function() { - const myMessage = 'Hi, how are you?'; - confirmModal(myMessage, { confirmButtonText: 'GREAT!', onConfirm: _.noop }); - - $rootScope.$digest(); - const message = findByDataTestSubj('confirmModalBodyText')[0].innerText.trim(); - expect(message).to.equal(myMessage); - }); - - describe('shows custom text', function() { - const confirmModalOptions = { - confirmButtonText: 'Troodon', - cancelButtonText: 'Dilophosaurus', - title: 'Dinosaurs', - onConfirm: _.noop, - }; - - it('for confirm button', () => { - confirmModal("What's your favorite dinosaur?", confirmModalOptions); - $rootScope.$digest(); - const confirmButtonText = findByDataTestSubj('confirmModalConfirmButton')[0].innerText.trim(); - expect(confirmButtonText).to.equal('Troodon'); - }); - - it('for cancel button', () => { - confirmModal("What's your favorite dinosaur?", confirmModalOptions); - $rootScope.$digest(); - const cancelButtonText = findByDataTestSubj('confirmModalCancelButton')[0].innerText.trim(); - expect(cancelButtonText).to.equal('Dilophosaurus'); - }); - - it('for title text', () => { - confirmModal("What's your favorite dinosaur?", confirmModalOptions); - $rootScope.$digest(); - const titleText = findByDataTestSubj('confirmModalTitleText')[0].innerText.trim(); - expect(titleText).to.equal('Dinosaurs'); - }); - }); - - describe('callbacks are called:', function() { - const confirmCallback = sinon.spy(); - const cancelCallback = sinon.spy(); - - const confirmModalOptions = { - confirmButtonText: 'bye', - onConfirm: confirmCallback, - onCancel: cancelCallback, - title: 'hi', - }; - - beforeEach(() => { - confirmCallback.resetHistory(); - cancelCallback.resetHistory(); - }); - - it('onCancel', function() { - confirmModal('hi', confirmModalOptions); - $rootScope.$digest(); - findByDataTestSubj('confirmModalCancelButton').click(); - - expect(confirmCallback.called).to.be(false); - expect(cancelCallback.called).to.be(true); - }); - - it('onConfirm', function() { - confirmModal('hi', confirmModalOptions); - $rootScope.$digest(); - findByDataTestSubj('confirmModalConfirmButton').click(); - - expect(confirmCallback.called).to.be(true); - expect(cancelCallback.called).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/modals/__tests__/confirm_modal_promise.js b/src/legacy/ui/public/modals/__tests__/confirm_modal_promise.js deleted file mode 100644 index 0967b3caefbbb..0000000000000 --- a/src/legacy/ui/public/modals/__tests__/confirm_modal_promise.js +++ /dev/null @@ -1,115 +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 expect from '@kbn/expect'; -import testSubjSelector from '@kbn/test-subj-selector'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import $ from 'jquery'; - -describe('ui/modals/confirm_modal_promise', function() { - let $rootScope; - let message; - let confirmModalPromise; - let promise; - - beforeEach(function() { - ngMock.module('kibana'); - ngMock.inject(function($injector) { - confirmModalPromise = $injector.get('confirmModalPromise'); - $rootScope = $injector.get('$rootScope'); - }); - - message = 'woah'; - - promise = confirmModalPromise(message, { confirmButtonText: 'click me' }); - }); - - afterEach(function() { - $rootScope.$digest(); - $(testSubjSelector('confirmModalConfirmButton')).click(); - }); - - describe('before timeout completes', function() { - it('returned promise is not resolved', function() { - const callback = sinon.spy(); - promise.then(callback, callback); - $rootScope.$apply(); - expect(callback.called).to.be(false); - }); - }); - - describe('after timeout completes', function() { - it('confirmation dialogue is loaded to dom with message', function() { - $rootScope.$digest(); - const confirmModalElement = $(testSubjSelector('confirmModal')); - expect(confirmModalElement).to.not.be(undefined); - - const htmlString = confirmModalElement[0].innerHTML; - - expect(htmlString.indexOf(message)).to.be.greaterThan(0); - }); - - describe('when confirmed', function() { - it('promise is fulfilled with true', function() { - const confirmCallback = sinon.spy(); - const cancelCallback = sinon.spy(); - - promise.then(confirmCallback, cancelCallback); - $rootScope.$digest(); - const confirmButton = $(testSubjSelector('confirmModalConfirmButton')); - - confirmButton.click(); - expect(confirmCallback.called).to.be(true); - expect(cancelCallback.called).to.be(false); - }); - }); - - describe('when canceled', function() { - it('promise is rejected with false', function() { - const confirmCallback = sinon.spy(); - const cancelCallback = sinon.spy(); - promise.then(confirmCallback, cancelCallback); - - $rootScope.$digest(); - const noButton = $(testSubjSelector('confirmModalCancelButton')); - noButton.click(); - - expect(cancelCallback.called).to.be(true); - expect(confirmCallback.called).to.be(false); - }); - }); - - describe('error is thrown', function() { - it('when no confirm button text is used', function() { - const confirmCallback = sinon.spy(); - const cancelCallback = sinon.spy(); - confirmModalPromise(message).then(confirmCallback, cancelCallback); - - $rootScope.$digest(); - sinon.assert.notCalled(confirmCallback); - sinon.assert.calledOnce(cancelCallback); - sinon.assert.calledWithExactly( - cancelCallback, - sinon.match.has('message', sinon.match(/confirmation button text/)) - ); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/modals/confirm_modal.html b/src/legacy/ui/public/modals/confirm_modal.html deleted file mode 100644 index 3eabe81fe9bd3..0000000000000 --- a/src/legacy/ui/public/modals/confirm_modal.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/src/legacy/ui/public/modals/confirm_modal.js b/src/legacy/ui/public/modals/confirm_modal.js deleted file mode 100644 index c609beff2fb16..0000000000000 --- a/src/legacy/ui/public/modals/confirm_modal.js +++ /dev/null @@ -1,119 +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 angular from 'angular'; -import { i18n } from '@kbn/i18n'; -import { noop } from 'lodash'; -import { uiModules } from '../modules'; -import template from './confirm_modal.html'; -import { ModalOverlay } from './modal_overlay'; - -const module = uiModules.get('kibana'); - -import { - EUI_MODAL_CONFIRM_BUTTON as CONFIRM_BUTTON, - EUI_MODAL_CANCEL_BUTTON as CANCEL_BUTTON, -} from '@elastic/eui'; - -export const ConfirmationButtonTypes = { - CONFIRM: CONFIRM_BUTTON, - CANCEL: CANCEL_BUTTON, -}; - -export function confirmModalFactory($rootScope, $compile) { - let modalPopover; - const confirmQueue = []; - - /** - * @param {String} message - the message to show in the body of the confirmation dialog. - * @param {ConfirmModalOptions} - Options to further customize the dialog. - */ - return function confirmModal(message, customOptions) { - const defaultOptions = { - onCancel: noop, - cancelButtonText: i18n.translate('common.ui.modals.cancelButtonLabel', { - defaultMessage: 'Cancel', - }), - defaultFocusedButton: ConfirmationButtonTypes.CONFIRM, - }; - - if (!customOptions.confirmButtonText || !customOptions.onConfirm) { - throw new Error('Please specify confirmation button text and onConfirm action'); - } - - const options = Object.assign(defaultOptions, customOptions); - - // Special handling for onClose - if no specific callback was supplied, default to the - // onCancel callback. - options.onClose = customOptions.onClose || options.onCancel; - - const confirmScope = $rootScope.$new(); - - confirmScope.message = message; - confirmScope.defaultFocusedButton = options.defaultFocusedButton; - confirmScope.confirmButtonText = options.confirmButtonText; - confirmScope.cancelButtonText = options.cancelButtonText; - confirmScope.title = options.title; - confirmScope.onConfirm = () => { - destroy(); - options.onConfirm(); - }; - confirmScope.onCancel = () => { - destroy(); - options.onCancel(); - }; - confirmScope.onClose = () => { - destroy(); - options.onClose(); - }; - - function showModal(confirmScope) { - const modalInstance = $compile(template)(confirmScope); - modalPopover = new ModalOverlay(modalInstance); - } - - if (modalPopover) { - confirmQueue.unshift(confirmScope); - } else { - showModal(confirmScope); - } - - function destroy() { - modalPopover.destroy(); - modalPopover = undefined; - angular.element(document.body).off('keydown'); - confirmScope.$destroy(); - - if (confirmQueue.length > 0) { - showModal(confirmQueue.pop()); - } - } - }; -} - -/** - * @typedef {Object} ConfirmModalOptions - * @property {String} confirmButtonText - * @property {String=} cancelButtonText - * @property {function} onConfirm - * @property {function=} onCancel - * @property {String=} title - If given, shows a title on the confirm modal. - */ - -module.factory('confirmModal', confirmModalFactory); diff --git a/src/legacy/ui/public/modals/confirm_modal_promise.js b/src/legacy/ui/public/modals/confirm_modal_promise.js deleted file mode 100644 index 54f568e80bff0..0000000000000 --- a/src/legacy/ui/public/modals/confirm_modal_promise.js +++ /dev/null @@ -1,49 +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 { uiModules } from '../modules'; -import './'; - -const module = uiModules.get('kibana'); - -/** - * @typedef {Object} PromisifiedConfirmOptions - * @property {String} confirmButtonText - * @property {String=} cancelButtonText - */ - -/** - * A "promisified" version of ConfirmModal that binds onCancel and onConfirm to - * Resolve and Reject methods. - */ -module.factory('confirmModalPromise', function(Promise, confirmModal) { - /** - * @param {String} message - * @param {PromisifiedConfirmOptions} customOptions - */ - return (message, customOptions) => - new Promise((resolve, reject) => { - const defaultOptions = { - onConfirm: resolve, - onCancel: reject, - }; - const confirmOptions = Object.assign(defaultOptions, customOptions); - confirmModal(message, confirmOptions); - }); -}); diff --git a/src/legacy/ui/public/modals/modal_overlay.html b/src/legacy/ui/public/modals/modal_overlay.html deleted file mode 100644 index 2abc5768f46f1..0000000000000 --- a/src/legacy/ui/public/modals/modal_overlay.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/legacy/ui/public/modals/modal_overlay.js b/src/legacy/ui/public/modals/modal_overlay.js deleted file mode 100644 index 6ddecee9f2f71..0000000000000 --- a/src/legacy/ui/public/modals/modal_overlay.js +++ /dev/null @@ -1,40 +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 angular from 'angular'; -import modalOverlayTemplate from './modal_overlay.html'; - -/** - * Appends the modal to the dom on instantiation, and removes it when destroy is called. - */ -export class ModalOverlay { - constructor(modalElement) { - this.overlayElement = angular.element(modalOverlayTemplate); - this.overlayElement.append(modalElement); - - angular.element(document.body).append(this.overlayElement); - } - - /** - * Removes the overlay and modal from the dom. - */ - destroy() { - this.overlayElement.remove(); - } -} diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3cc33504d3daa..47ef690c4f83e 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -176,7 +176,11 @@ let isAutoRefreshSelectorEnabled = true; export const npStart = { core: { - chrome: {}, + chrome: { + overlays: { + openModal: sinon.fake(), + }, + }, }, plugins: { management: { @@ -212,6 +216,10 @@ export const npStart = { config: { defaultAppId: 'home', }, + dashboardConfig: { + turnHideWriteControlsOn: sinon.fake(), + getHideWriteControls: sinon.fake(), + }, }, data: { autocomplete: { diff --git a/src/legacy/ui/public/react_components.js b/src/legacy/ui/public/react_components.js index fea25d2c71da3..b771e37c9d538 100644 --- a/src/legacy/ui/public/react_components.js +++ b/src/legacy/ui/public/react_components.js @@ -19,14 +19,12 @@ import 'ngreact'; -import { EuiConfirmModal, EuiIcon, EuiIconTip } from '@elastic/eui'; +import { EuiIcon, EuiIconTip } from '@elastic/eui'; import { uiModules } from './modules'; const app = uiModules.get('app/kibana', ['react']); -app.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); - app.directive('icon', reactDirective => reactDirective(EuiIcon)); app.directive('iconTip', reactDirective => diff --git a/src/legacy/ui/public/registry/feature_catalogue.js b/src/legacy/ui/public/registry/feature_catalogue.js deleted file mode 100644 index 23aaf2fb0a1d9..0000000000000 --- a/src/legacy/ui/public/registry/feature_catalogue.js +++ /dev/null @@ -1,33 +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 { uiRegistry } from './_registry'; -import { capabilities } from '../capabilities'; -export { FeatureCatalogueCategory } from '../../../../plugins/home/public'; - -export const FeatureCatalogueRegistryProvider = uiRegistry({ - name: 'featureCatalogue', - index: ['id'], - group: ['category'], - order: ['title'], - filter: featureCatalogItem => { - const isDisabledViaCapabilities = capabilities.get().catalogue[featureCatalogItem.id] === false; - return !isDisabledViaCapabilities && Object.keys(featureCatalogItem).length > 0; - }, -}); diff --git a/src/legacy/ui/public/registry/feature_catalogue.test.js b/src/legacy/ui/public/registry/feature_catalogue.test.js deleted file mode 100644 index 15aed78143882..0000000000000 --- a/src/legacy/ui/public/registry/feature_catalogue.test.js +++ /dev/null @@ -1,101 +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. - */ -jest.mock('ui/capabilities', () => ({ - capabilities: { - get: () => ({ - navLinks: {}, - management: {}, - catalogue: { - item1: true, - item2: false, - item3: true, - }, - }), - }, -})); -import { FeatureCatalogueCategory, FeatureCatalogueRegistryProvider } from './feature_catalogue'; - -describe('FeatureCatalogueRegistryProvider', () => { - beforeAll(() => { - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'item1', - title: 'foo', - description: 'this is foo', - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; - }); - - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'item2', - title: 'bar', - description: 'this is bar', - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; - }); - - // intentionally not listed in uiCapabilities.catalogue above - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'item4', - title: 'secret', - description: 'this is a secret', - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; - }); - }); - - it('should not return items hidden by uiCapabilities', () => { - const mockPrivate = entityFn => entityFn(); - const mockInjector = () => null; - - // eslint-disable-next-line new-cap - const foo = FeatureCatalogueRegistryProvider(mockPrivate, mockInjector).inTitleOrder; - expect(foo).toEqual([ - { - id: 'item1', - title: 'foo', - description: 'this is foo', - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }, - { - id: 'item4', - title: 'secret', - description: 'this is a secret', - icon: 'savedObjectsApp', - path: '/app/kibana#/management/kibana/objects', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }, - ]); - }); -}); diff --git a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js index ace87a15f7b58..d027d8b6c99da 100644 --- a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js +++ b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js @@ -26,7 +26,7 @@ import { createSavedObjectClass } from '../saved_object'; import StubIndexPattern from 'test_utils/stub_index_pattern'; import { npStart } from 'ui/new_platform'; import { InvalidJSONProperty } from '../../../../../plugins/kibana_utils/public'; -import { npSetup } from '../../new_platform/new_platform.karma_mock'; +import { npSetup, npStart as npStartMock } from '../../new_platform/new_platform.karma_mock'; const getConfig = cfg => cfg; @@ -89,18 +89,12 @@ describe('Saved Object', function() { obj[fName].restore && obj[fName].restore(); } - beforeEach( - ngMock.module( - 'kibana', - // Use the native window.confirm instead of our specialized version to make testing - // this easier. - function($provide) { - const overrideConfirm = message => - window.confirm(message) ? Promise.resolve() : Promise.reject(); - $provide.decorator('confirmModalPromise', () => overrideConfirm); - } - ) - ); + beforeEach(() => { + // Use the native window.confirm instead of our specialized version to make testing + // this easier. + npStartMock.core.overlays.openModal = message => + window.confirm(message) ? Promise.resolve() : Promise.reject(); + }); beforeEach( ngMock.inject(function($window) { diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index d8227343159e6..7228e506accb9 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -22,7 +22,12 @@ import { identity } from 'lodash'; import { IAggConfig } from 'ui/agg_types'; import { npStart } from 'ui/new_platform'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { fieldFormats } from '../../../../../../plugins/data/public'; +import { + fieldFormats, + IFieldFormat, + FieldFormatId, + FieldFormatsContentType, +} from '../../../../../../plugins/data/public'; import { Vis } from '../../../../../core_plugins/visualizations/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; @@ -45,10 +50,7 @@ const getConfig = (key: string, defaultOverride?: any): any => npStart.core.uiSettings.get(key, defaultOverride); const DefaultFieldFormat = fieldFormats.FieldFormat.from(identity); -const getFieldFormat = ( - id?: fieldFormats.IFieldFormatId, - params: object = {} -): fieldFormats.FieldFormat => { +const getFieldFormat = (id?: FieldFormatId, params: object = {}): IFieldFormat => { const fieldFormatsService = npStart.plugins.data.fieldFormats; if (id) { @@ -94,7 +96,7 @@ export const createFormat = (agg: IAggConfig): SerializedFieldFormat => { return formats[agg.type.name] ? formats[agg.type.name]() : format; }; -export type FormatFactory = (mapping?: SerializedFieldFormat) => fieldFormats.FieldFormat; +export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat; export const getFormat: FormatFactory = mapping => { if (!mapping) { @@ -133,7 +135,7 @@ export const getFormat: FormatFactory = mapping => { return new IpRangeFormat(); } else if (isTermsFieldFormat(mapping) && mapping.params) { const { params } = mapping; - const convert = (val: string, type: fieldFormats.ContentType) => { + const convert = (val: string, type: FieldFormatsContentType) => { const format = getFieldFormat(params.id, mapping.params); if (val === '__other__') { @@ -148,8 +150,8 @@ export const getFormat: FormatFactory = mapping => { return { convert, - getConverterFor: (type: fieldFormats.ContentType) => (val: string) => convert(val, type), - } as fieldFormats.FieldFormat; + getConverterFor: (type: FieldFormatsContentType) => (val: string) => convert(val, type), + } as IFieldFormat; } else { return getFieldFormat(id, mapping.params); } diff --git a/src/plugins/advanced_settings/kibana.json b/src/plugins/advanced_settings/kibana.json index 5fc1e916ae45f..bafb2caba32be 100644 --- a/src/plugins/advanced_settings/kibana.json +++ b/src/plugins/advanced_settings/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [] + "requiredPlugins": ["home"] } diff --git a/src/plugins/advanced_settings/public/mocks.ts b/src/plugins/advanced_settings/public/mocks.ts index e147f57101aae..b44561959cbd3 100644 --- a/src/plugins/advanced_settings/public/mocks.ts +++ b/src/plugins/advanced_settings/public/mocks.ts @@ -25,9 +25,9 @@ const componentType = ComponentRegistry.componentType; export const advancedSettingsMock = { createSetupContract() { - return { register, componentType }; + return { component: { register, componentType } }; }, createStartContract() { - return { get, componentType }; + return { component: { get, componentType } }; }, }; diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 692e515ca4e5e..bffd5a5157615 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -17,15 +17,31 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; +import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public'; const component = new ComponentRegistry(); export class AdvancedSettingsPlugin implements Plugin { - public setup(core: CoreSetup) { + public setup(core: CoreSetup, { home }: { home: HomePublicPluginSetup }) { + home.featureCatalogue.register({ + id: 'advanced_settings', + title: i18n.translate('advancedSettings.advancedSettingsLabel', { + defaultMessage: 'Advanced Settings', + }), + description: i18n.translate('advancedSettings.advancedSettingsDescription', { + defaultMessage: 'Directly edit settings that control behavior in Kibana.', + }), + icon: 'advancedSettingsApp', + path: '/app/kibana#/management/kibana/settings', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, + }); + return { component: component.setup, }; diff --git a/src/plugins/data/common/field_formats/content_types/html_content_type.ts b/src/plugins/data/common/field_formats/content_types/html_content_type.ts index 1b6ee9e63fad1..d4701200d99e0 100644 --- a/src/plugins/data/common/field_formats/content_types/html_content_type.ts +++ b/src/plugins/data/common/field_formats/content_types/html_content_type.ts @@ -17,10 +17,10 @@ * under the License. */ import { escape, isFunction } from 'lodash'; -import { IFieldFormat, HtmlContextTypeConvert } from '../types'; +import { IFieldFormat, HtmlContextTypeConvert, FieldFormatsContentType } from '../types'; import { asPrettyString, getHighlightHtml } from '../utils'; -export const HTML_CONTEXT_TYPE = 'html'; +export const HTML_CONTEXT_TYPE: FieldFormatsContentType = 'html'; const getConvertFn = ( format: IFieldFormat, diff --git a/src/plugins/data/common/field_formats/content_types/text_content_type.ts b/src/plugins/data/common/field_formats/content_types/text_content_type.ts index c91b7c6d1c18c..dc450086edc62 100644 --- a/src/plugins/data/common/field_formats/content_types/text_content_type.ts +++ b/src/plugins/data/common/field_formats/content_types/text_content_type.ts @@ -18,10 +18,10 @@ */ import { isFunction } from 'lodash'; -import { IFieldFormat, TextContextTypeConvert } from '../types'; +import { IFieldFormat, TextContextTypeConvert, FieldFormatsContentType } from '../types'; import { asPrettyString } from '../utils'; -export const TEXT_CONTEXT_TYPE = 'text'; +export const TEXT_CONTEXT_TYPE: FieldFormatsContentType = 'text'; export const setup = ( format: IFieldFormat, diff --git a/src/plugins/data/common/field_formats/converters/date_server.ts b/src/plugins/data/common/field_formats/converters/date_server.ts index 9be8f066539f1..216af133bb5f5 100644 --- a/src/plugins/data/common/field_formats/converters/date_server.ts +++ b/src/plugins/data/common/field_formats/converters/date_server.ts @@ -25,7 +25,7 @@ import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS, - GetConfigFn, + FieldFormatsGetConfigFn, IFieldFormatMetaParams, } from '../types'; @@ -40,7 +40,7 @@ export class DateFormat extends FieldFormat { private memoizedPattern: string = ''; private timeZone: string = ''; - constructor(params: IFieldFormatMetaParams, getConfig?: GetConfigFn) { + constructor(params: IFieldFormatMetaParams, getConfig?: FieldFormatsGetConfigFn) { super(params, getConfig); this.memoizedConverter = memoize((val: any) => { diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts index d605dcd2e78ac..49baa8c074da8 100644 --- a/src/plugins/data/common/field_formats/field_format.ts +++ b/src/plugins/data/common/field_formats/field_format.ts @@ -20,8 +20,8 @@ import { transform, size, cloneDeep, get, defaults } from 'lodash'; import { createCustomFieldFormat } from './converters/custom'; import { - GetConfigFn, - ContentType, + FieldFormatsGetConfigFn, + FieldFormatsContentType, IFieldFormatType, FieldFormatConvert, FieldFormatConvertFunction, @@ -29,12 +29,7 @@ import { TextContextTypeOptions, IFieldFormatMetaParams, } from './types'; -import { - htmlContentTypeSetup, - textContentTypeSetup, - TEXT_CONTEXT_TYPE, - HTML_CONTEXT_TYPE, -} from './content_types'; +import { htmlContentTypeSetup, textContentTypeSetup, TEXT_CONTEXT_TYPE } from './content_types'; import { HtmlContextTypeConvert, TextContextTypeConvert } from './types'; const DEFAULT_CONTEXT_TYPE = TEXT_CONTEXT_TYPE; @@ -90,9 +85,9 @@ export abstract class FieldFormat { public type: any = this.constructor; protected readonly _params: any; - protected getConfig: GetConfigFn | undefined; + protected getConfig: FieldFormatsGetConfigFn | undefined; - constructor(_params: IFieldFormatMetaParams = {}, getConfig?: GetConfigFn) { + constructor(_params: IFieldFormatMetaParams = {}, getConfig?: FieldFormatsGetConfigFn) { this._params = _params; if (getConfig) { @@ -112,7 +107,7 @@ export abstract class FieldFormat { */ convert( value: any, - contentType: ContentType = DEFAULT_CONTEXT_TYPE, + contentType: FieldFormatsContentType = DEFAULT_CONTEXT_TYPE, options?: HtmlContextTypeOptions | TextContextTypeOptions ): string { const converter = this.getConverterFor(contentType); @@ -131,7 +126,7 @@ export abstract class FieldFormat { * @public */ getConverterFor( - contentType: ContentType = DEFAULT_CONTEXT_TYPE + contentType: FieldFormatsContentType = DEFAULT_CONTEXT_TYPE ): FieldFormatConvertFunction | null { if (!this.convertObject) { this.convertObject = this.setupContentType(); @@ -210,8 +205,8 @@ export abstract class FieldFormat { setupContentType(): FieldFormatConvert { return { - [TEXT_CONTEXT_TYPE]: textContentTypeSetup(this, this.textConvert), - [HTML_CONTEXT_TYPE]: htmlContentTypeSetup(this, this.htmlConvert), + text: textContentTypeSetup(this, this.textConvert), + html: htmlContentTypeSetup(this, this.htmlConvert), }; } diff --git a/src/plugins/data/common/field_formats/field_formats_registry.test.ts b/src/plugins/data/common/field_formats/field_formats_registry.test.ts index 5f6f9fdf897ff..0b32a62744fb1 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.test.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.test.ts @@ -18,7 +18,7 @@ */ import { FieldFormatsRegistry } from './field_formats_registry'; import { BoolFormat, PercentFormat, StringFormat } from './converters'; -import { GetConfigFn, IFieldFormatType } from './types'; +import { FieldFormatsGetConfigFn, IFieldFormatType } from './types'; import { KBN_FIELD_TYPES } from '../../common'; const getValueOfPrivateField = (instance: any, field: string) => instance[field]; @@ -26,7 +26,7 @@ const getValueOfPrivateField = (instance: any, field: string) => instance[field] describe('FieldFormatsRegistry', () => { let fieldFormatsRegistry: FieldFormatsRegistry; let defaultMap = {}; - const getConfig = (() => defaultMap) as GetConfigFn; + const getConfig = (() => defaultMap) as FieldFormatsGetConfigFn; beforeEach(() => { fieldFormatsRegistry = new FieldFormatsRegistry(); diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index 9b85921b820c8..6e4880a221c46 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -23,24 +23,24 @@ import { forOwn, isFunction, memoize } from 'lodash'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../common'; import { - GetConfigFn, - IFieldFormatConfig, + FieldFormatsGetConfigFn, + FieldFormatConfig, FIELD_FORMAT_IDS, IFieldFormatType, - IFieldFormatId, + FieldFormatId, IFieldFormatMetaParams, } from './types'; import { baseFormatters } from './constants/base_formatters'; import { FieldFormat } from './field_format'; export class FieldFormatsRegistry { - protected fieldFormats: Map = new Map(); - protected defaultMap: Record = {}; + protected fieldFormats: Map = new Map(); + protected defaultMap: Record = {}; protected metaParamsOptions: Record = {}; - protected getConfig?: GetConfigFn; + protected getConfig?: FieldFormatsGetConfigFn; init( - getConfig: GetConfigFn, + getConfig: FieldFormatsGetConfigFn, metaParamsOptions: Record = {}, defaultFieldConverters: IFieldFormatType[] = baseFormatters ) { @@ -62,7 +62,7 @@ export class FieldFormatsRegistry { getDefaultConfig = ( fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[] - ): IFieldFormatConfig => { + ): FieldFormatConfig => { const type = this.getDefaultTypeName(fieldType, esTypes); return ( @@ -73,10 +73,10 @@ export class FieldFormatsRegistry { /** * Get a derived FieldFormat class by its id. * - * @param {IFieldFormatId} formatId - the format id - * @return {FieldFormat | undefined} + * @param {FieldFormatId} formatId - the format id + * @return {IFieldFormatType | undefined} */ - getType = (formatId: IFieldFormatId): IFieldFormatType | undefined => { + getType = (formatId: FieldFormatId): IFieldFormatType | undefined => { const fieldFormat = this.fieldFormats.get(formatId); if (fieldFormat) { @@ -97,7 +97,7 @@ export class FieldFormatsRegistry { * * @param {KBN_FIELD_TYPES} fieldType * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types - * @return {FieldFormat | undefined} + * @return {IFieldFormatType | undefined} */ getDefaultType = ( fieldType: KBN_FIELD_TYPES, @@ -129,7 +129,7 @@ export class FieldFormatsRegistry { * * @param {KBN_FIELD_TYPES} fieldType * @param {ES_FIELD_TYPES[]} esTypes - * @return {ES_FIELD_TYPES | String} + * @return {ES_FIELD_TYPES | KBN_FIELD_TYPES} */ getDefaultTypeName = ( fieldType: KBN_FIELD_TYPES, @@ -143,11 +143,11 @@ export class FieldFormatsRegistry { /** * Get the singleton instance of the FieldFormat type by its id. * - * @param {IFieldFormatId} formatId - * @return {FIELD_FORMATS_INSTANCES[number]} + * @param {FieldFormatId} formatId + * @return {FieldFormat} */ getInstance = memoize( - (formatId: IFieldFormatId, params: Record = {}): FieldFormat => { + (formatId: FieldFormatId, params: Record = {}): FieldFormat => { const ConcreteFieldFormat = this.getType(formatId); if (!ConcreteFieldFormat) { @@ -156,7 +156,7 @@ export class FieldFormatsRegistry { return new ConcreteFieldFormat(params, this.getConfig); }, - (formatId: IFieldFormatId, params: Record) => + (formatId: FieldFormatId, params: Record) => JSON.stringify({ formatId, ...params, @@ -197,7 +197,7 @@ export class FieldFormatsRegistry { * Get filtered list of field formats by format type * * @param {KBN_FIELD_TYPES} fieldType - * @return {FieldFormat[]} + * @return {IFieldFormatType[]} */ getByFieldType(fieldType: KBN_FIELD_TYPES): IFieldFormatType[] { return [...this.fieldFormats.values()] @@ -238,7 +238,7 @@ export class FieldFormatsRegistry { * * @private * @param {IFieldFormatType} fieldFormat - field format type - * @return {FieldFormat | undefined} + * @return {IFieldFormatType | undefined} */ private fieldFormatMetaParamsDecorator = ( fieldFormat: IFieldFormatType @@ -250,7 +250,7 @@ export class FieldFormatsRegistry { static id = fieldFormat.id; static fieldType = fieldFormat.fieldType; - constructor(params: Record = {}, getConfig?: GetConfigFn) { + constructor(params: Record = {}, getConfig?: FieldFormatsGetConfigFn) { super(getMetaParams(params), getConfig); } }; diff --git a/src/plugins/data/common/field_formats/index.ts b/src/plugins/data/common/field_formats/index.ts index e54903375dcf1..0847ac0db745f 100644 --- a/src/plugins/data/common/field_formats/index.ts +++ b/src/plugins/data/common/field_formats/index.ts @@ -17,6 +17,42 @@ * under the License. */ -import * as fieldFormats from './static'; +import { FieldFormatsRegistry } from './field_formats_registry'; +type IFieldFormatsRegistry = PublicMethodsOf; -export { fieldFormats }; +export { FieldFormatsRegistry, IFieldFormatsRegistry }; +export { FieldFormat } from './field_format'; +export { baseFormatters } from './constants/base_formatters'; +export { + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + UrlFormat, + StringFormat, + TruncateFormat, +} from './converters'; + +export { getHighlightRequest } from './utils'; + +export { DEFAULT_CONVERTER_COLOR } from './constants/color_default'; +export { FIELD_FORMAT_IDS } from './types'; +export { HTML_CONTEXT_TYPE, TEXT_CONTEXT_TYPE } from './content_types'; + +export { + FieldFormatsGetConfigFn, + FieldFormatsContentType, + FieldFormatConfig, + FieldFormatId, + // Used in data plugin only + IFieldFormatType, + IFieldFormat, +} from './types'; diff --git a/src/plugins/data/common/field_formats/static.ts b/src/plugins/data/common/field_formats/static.ts deleted file mode 100644 index 186a0ff6ede5c..0000000000000 --- a/src/plugins/data/common/field_formats/static.ts +++ /dev/null @@ -1,58 +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. - */ - -/** - * Everything the file exports is public - */ - -export { HTML_CONTEXT_TYPE, TEXT_CONTEXT_TYPE } from './content_types'; -export { FieldFormat } from './field_format'; -export { FieldFormatsRegistry } from './field_formats_registry'; -export { getHighlightRequest, asPrettyString, getHighlightHtml } from './utils'; - -export { baseFormatters } from './constants/base_formatters'; -export { DEFAULT_CONVERTER_COLOR } from './constants/color_default'; - -export { - BoolFormat, - BytesFormat, - ColorFormat, - DateFormat, - DateNanosFormat, - DurationFormat, - IpFormat, - NumberFormat, - PercentFormat, - RelativeDateFormat, - SourceFormat, - StaticLookupFormat, - UrlFormat, - StringFormat, - TruncateFormat, -} from './converters'; - -export { - GetConfigFn, - FIELD_FORMAT_IDS, - ContentType, - IFieldFormatConfig, - IFieldFormatType, - IFieldFormat, - IFieldFormatId, -} from './types'; diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index b6c10c9964f67..24aa92c67b694 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -20,7 +20,7 @@ import { FieldFormat } from './field_format'; /** @public **/ -export type ContentType = 'html' | 'text'; +export type FieldFormatsContentType = 'html' | 'text'; /** @internal **/ export interface HtmlContextTypeOptions { @@ -66,23 +66,26 @@ export enum FIELD_FORMAT_IDS { URL = 'url', } -export interface IFieldFormatConfig { - id: IFieldFormatId; +export interface FieldFormatConfig { + id: FieldFormatId; params: Record; es?: boolean; } -export type GetConfigFn = (key: string, defaultOverride?: T) => T; +export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; export type IFieldFormat = PublicMethodsOf; /** * @string id type is needed for creating custom converters. */ -export type IFieldFormatId = FIELD_FORMAT_IDS | string; +export type FieldFormatId = FIELD_FORMAT_IDS | string; -export type IFieldFormatType = (new (params?: any, getConfig?: GetConfigFn) => FieldFormat) & { - id: IFieldFormatId; +export type IFieldFormatType = (new ( + params?: any, + getConfig?: FieldFormatsGetConfigFn +) => FieldFormat) & { + id: FieldFormatId; fieldType: string | string[]; }; diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts index 78bd2ec85f477..bc557f31f7466 100644 --- a/src/plugins/data/public/autocomplete/autocomplete_service.ts +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -18,26 +18,23 @@ */ import { CoreSetup } from 'src/core/public'; -import { QuerySuggestionsGetFn } from './providers/query_suggestion_provider'; +import { QuerySuggestionGetFn } from './providers/query_suggestion_provider'; import { setupValueSuggestionProvider, ValueSuggestionsGetFn, } from './providers/value_suggestion_provider'; export class AutocompleteService { - private readonly querySuggestionProviders: Map = new Map(); + private readonly querySuggestionProviders: Map = new Map(); private getValueSuggestions?: ValueSuggestionsGetFn; - private addQuerySuggestionProvider = ( - language: string, - provider: QuerySuggestionsGetFn - ): void => { + private addQuerySuggestionProvider = (language: string, provider: QuerySuggestionGetFn): void => { if (language && provider) { this.querySuggestionProviders.set(language, provider); } }; - private getQuerySuggestions: QuerySuggestionsGetFn = args => { + private getQuerySuggestions: QuerySuggestionGetFn = args => { const { language } = args; const provider = this.querySuggestionProviders.get(language); diff --git a/src/plugins/data/public/autocomplete/index.ts b/src/plugins/data/public/autocomplete/index.ts index c2b21e84b7a38..d5bf4e2fd211b 100644 --- a/src/plugins/data/public/autocomplete/index.ts +++ b/src/plugins/data/public/autocomplete/index.ts @@ -16,7 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import * as autocomplete from './static'; -export { AutocompleteService, AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; -export { autocomplete }; +export { + QuerySuggestion, + QuerySuggestionTypes, + QuerySuggestionGetFn, + QuerySuggestionGetFnArgs, + QuerySuggestionBasic, + QuerySuggestionField, +} from './providers/query_suggestion_provider'; + +export { AutocompleteService, AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; diff --git a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 94054ed56f42a..16500ac9e239a 100644 --- a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -19,7 +19,7 @@ import { IFieldType, IIndexPattern } from '../../../common/index_patterns'; -export enum QuerySuggestionsTypes { +export enum QuerySuggestionTypes { Field = 'field', Value = 'value', Operator = 'operator', @@ -27,12 +27,12 @@ export enum QuerySuggestionsTypes { RecentSearch = 'recentSearch', } -export type QuerySuggestionsGetFn = ( - args: QuerySuggestionsGetFnArgs +export type QuerySuggestionGetFn = ( + args: QuerySuggestionGetFnArgs ) => Promise | undefined; /** @public **/ -export interface QuerySuggestionsGetFnArgs { +export interface QuerySuggestionGetFnArgs { language: string; indexPatterns: IIndexPattern[]; query: string; @@ -43,8 +43,8 @@ export interface QuerySuggestionsGetFnArgs { } /** @public **/ -export interface BasicQuerySuggestion { - type: QuerySuggestionsTypes; +export interface QuerySuggestionBasic { + type: QuerySuggestionTypes; description?: string | JSX.Element; end: number; start: number; @@ -53,10 +53,10 @@ export interface BasicQuerySuggestion { } /** @public **/ -export interface FieldQuerySuggestion extends BasicQuerySuggestion { - type: QuerySuggestionsTypes.Field; +export interface QuerySuggestionField extends QuerySuggestionBasic { + type: QuerySuggestionTypes.Field; field: IFieldType; } /** @public **/ -export type QuerySuggestion = BasicQuerySuggestion | FieldQuerySuggestion; +export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; diff --git a/src/plugins/data/public/field_formats/field_formats_service.ts b/src/plugins/data/public/field_formats/field_formats_service.ts index 68df0aa254580..1c43ce2198645 100644 --- a/src/plugins/data/public/field_formats/field_formats_service.ts +++ b/src/plugins/data/public/field_formats/field_formats_service.ts @@ -18,10 +18,10 @@ */ import { CoreSetup } from 'src/core/public'; -import { fieldFormats } from '../../common/field_formats'; +import { FieldFormatsRegistry } from '../../common/field_formats'; export class FieldFormatsService { - private readonly fieldFormatsRegistry: fieldFormats.FieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); + private readonly fieldFormatsRegistry: FieldFormatsRegistry = new FieldFormatsRegistry(); public setup(core: CoreSetup) { core.uiSettings.getUpdate$().subscribe(({ key, newValue }) => { @@ -49,7 +49,7 @@ export class FieldFormatsService { } /** @public */ -export type FieldFormatsSetup = Pick; +export type FieldFormatsSetup = Pick; /** @public */ -export type FieldFormatsStart = Omit; +export type FieldFormatsStart = Omit; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 2fa6b8deae69d..6c14739d42bf1 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -19,6 +19,60 @@ import { PluginInitializerContext } from '../../../core/public'; +/* + * Field Formatters helper namespace: + */ + +import { + FieldFormat, + FieldFormatsRegistry, // exported only for tests. Consider mock. + DEFAULT_CONVERTER_COLOR, + HTML_CONTEXT_TYPE, + TEXT_CONTEXT_TYPE, + FIELD_FORMAT_IDS, + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + UrlFormat, + StringFormat, + TruncateFormat, +} from '../common/field_formats'; + +export const fieldFormats = { + FieldFormat, + FieldFormatsRegistry, // exported only for tests. Consider mock. + + DEFAULT_CONVERTER_COLOR, + HTML_CONTEXT_TYPE, + TEXT_CONTEXT_TYPE, + FIELD_FORMAT_IDS, + + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + UrlFormat, + StringFormat, + TruncateFormat, +}; + export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } @@ -42,8 +96,24 @@ export { // timefilter RefreshInterval, TimeRange, + // Field Formats + IFieldFormat, + IFieldFormatsRegistry, + FieldFormatsContentType, + FieldFormatsGetConfigFn, + FieldFormatConfig, + FieldFormatId, } from '../common'; -export { autocomplete } from './autocomplete'; + +export { + QuerySuggestion, + QuerySuggestionTypes, + QuerySuggestionGetFn, + QuerySuggestionGetFnArgs, + QuerySuggestionBasic, + QuerySuggestionField, +} from './autocomplete'; + export * from './field_formats'; export * from './index_patterns'; export * from './search'; @@ -54,7 +124,6 @@ export { esFilters, esKuery, esQuery, - fieldFormats, // index patterns isFilterable, // kbn field types diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 730a2f88c5eb7..f59fbefbea451 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -26,8 +26,8 @@ import { IFieldType, getKbnFieldType, IFieldSubType, - fieldFormats, shortenDottedString, + FieldFormat, } from '../../../common'; export type FieldSpec = Record; @@ -95,7 +95,7 @@ export class Field implements IFieldType { let format = spec.format; - if (!fieldFormats.FieldFormat.isInstanceOfFieldFormat(format)) { + if (!FieldFormat.isInstanceOfFieldFormat(format)) { const fieldFormatsService = getFieldFormats(); format = diff --git a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts index f9e15c8650ce0..9b18fb98f3e02 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import { IndexPattern } from './index_pattern'; -import { fieldFormats } from '../../../common'; +import { FieldFormatsContentType } from '../../../common'; const formattedCache = new WeakMap(); const partialFormattedCache = new WeakMap(); @@ -31,7 +31,7 @@ export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any hit: Record, val: any, fieldName: string, - type: fieldFormats.ContentType = 'html' + type: FieldFormatsContentType = 'html' ) { const field = indexPattern.fields.getByName(fieldName); const format = field ? field.format : defaultFormat; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index 059df6d4eb0f8..103f9d385b3f9 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -31,7 +31,7 @@ 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 { fieldFormats } from '../../../common/field_formats'; +import { FieldFormatsRegistry } from '../../../common/field_formats'; jest.mock('../../../../kibana_utils/public', () => { const originalModule = jest.requireActual('../../../../kibana_utils/public'); @@ -125,7 +125,7 @@ describe('IndexPattern', () => { setNotifications(notifications); setFieldFormats(({ getDefaultInstance: jest.fn(), - } as unknown) as fieldFormats.FieldFormatsRegistry); + } as unknown) as FieldFormatsRegistry); return create(indexPatternId).then((pattern: IndexPattern) => { indexPattern = pattern; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 726cd6cfb18f0..0a093644c3939 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -18,10 +18,10 @@ */ import { Plugin, - FieldFormatsStart, - FieldFormatsSetup, + DataPublicPluginSetup, + DataPublicPluginStart, IndexPatternsContract, - fieldFormats, + IFieldFormatsRegistry, } from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -35,7 +35,7 @@ const autocompleteMock: any = { hasQuerySuggestions: jest.fn(), }; -const fieldFormatsMock: PublicMethodsOf = { +const fieldFormatsMock: IFieldFormatsRegistry = { getByFieldType: jest.fn(), getDefaultConfig: jest.fn(), getDefaultInstance: jest.fn() as any, @@ -56,7 +56,7 @@ const createSetupContract = (): Setup => { const setupContract = { autocomplete: autocompleteMock, search: searchSetupMock, - fieldFormats: fieldFormatsMock as FieldFormatsSetup, + fieldFormats: fieldFormatsMock as DataPublicPluginSetup['fieldFormats'], query: querySetupMock, __LEGACY: { esClient: { @@ -84,7 +84,7 @@ const createStartContract = (): Start => { }, }, }, - fieldFormats: fieldFormatsMock as FieldFormatsStart, + fieldFormats: fieldFormatsMock as DataPublicPluginStart['fieldFormats'], query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts index fdd029c563cdd..70876b4e2be77 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts @@ -24,6 +24,7 @@ describe('mapSpatialFilter()', () => { test('should return the key for matching multi polygon filter', async () => { const filter = { meta: { + key: 'location', alias: 'my spatial filter', type: esFilters.FILTERS.SPATIAL_FILTER, } as esFilters.FilterMeta, @@ -41,7 +42,7 @@ describe('mapSpatialFilter()', () => { } as esFilters.Filter; const result = mapSpatialFilter(filter); - expect(result).toHaveProperty('key', 'query'); + expect(result).toHaveProperty('key', 'location'); expect(result).toHaveProperty('value', ''); expect(result).toHaveProperty('type', esFilters.FILTERS.SPATIAL_FILTER); }); @@ -49,6 +50,7 @@ describe('mapSpatialFilter()', () => { test('should return the key for matching polygon filter', async () => { const filter = { meta: { + key: 'location', alias: 'my spatial filter', type: esFilters.FILTERS.SPATIAL_FILTER, } as esFilters.FilterMeta, @@ -58,7 +60,7 @@ describe('mapSpatialFilter()', () => { } as esFilters.Filter; const result = mapSpatialFilter(filter); - expect(result).toHaveProperty('key', 'geo_polygon'); + expect(result).toHaveProperty('key', 'location'); expect(result).toHaveProperty('value', ''); expect(result).toHaveProperty('type', esFilters.FILTERS.SPATIAL_FILTER); }); @@ -66,6 +68,7 @@ describe('mapSpatialFilter()', () => { test('should return undefined for none matching', async done => { const filter = { meta: { + key: 'location', alias: 'my spatial filter', } as esFilters.FilterMeta, geo_polygon: { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts index 3cf1cf7835e69..ed2e5df82e37e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts @@ -20,18 +20,14 @@ import { esFilters } from '../../../../../common'; // Use mapSpatialFilter mapper to avoid bloated meta with value and params for spatial filters. export const mapSpatialFilter = (filter: esFilters.Filter) => { - const metaProperty = /(^\$|meta)/; - const key = Object.keys(filter).find(item => { - return !item.match(metaProperty); - }); if ( - key && filter.meta && + filter.meta.key && filter.meta.alias && filter.meta.type === esFilters.FILTERS.SPATIAL_FILTER ) { return { - key, + key: filter.meta.key, type: filter.meta.type, value: '', }; diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 7b1a4533dd1c6..1ebf9a8ca534c 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -73,11 +73,12 @@ import _ from 'lodash'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; -import { fieldFormats, esFilters, esQuery, SearchRequest } from '../..'; +import { esFilters, esQuery, SearchRequest } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { fetchSoon, FetchOptions, RequestFailure } from '../fetch'; import { getSearchService, getUiSettings, getInjectedMetadata } from '../../services'; +import { getHighlightRequest } from '../../../common'; export type ISearchSource = Pick; @@ -382,10 +383,7 @@ export class SearchSource { body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = fieldFormats.getHighlightRequest( - body.query, - getUiSettings().get('doc_table:highlight') - ); + body.highlight = getHighlightRequest(body.query, getUiSettings().get('doc_table:highlight')); delete searchRequest.highlightAll; } diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 6a15893f573d8..2af87d84b780e 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -19,7 +19,7 @@ import { NotificationsStart } from 'src/core/public'; import { CoreSetup, CoreStart } from 'kibana/public'; -import { FieldFormatsStart } from '.'; +import { FieldFormatsStart } from './field_formats'; import { createGetterSetter } from '../../kibana_utils/public'; import { IndexPatternsContract } from './index_patterns'; import { DataPublicPluginStart } from './types'; diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 7a8c0f7269fa1..f8cb4050efdfb 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -35,7 +35,6 @@ import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; import { Toast } from 'src/core/public'; import { - autocomplete, IDataPluginServices, IIndexPattern, PersistedLog, @@ -46,6 +45,8 @@ import { getQueryLog, Query, } from '../..'; +import { QuerySuggestion, QuerySuggestionTypes } from '../../autocomplete'; + import { withKibana, KibanaReactContextValue, toMountPoint } from '../../../../kibana_react/public'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; @@ -70,7 +71,7 @@ interface Props { interface State { isSuggestionsVisible: boolean; index: number | null; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; suggestionLimit: number; selectionStart: number | null; selectionEnd: number | null; @@ -191,7 +192,7 @@ export class QueryStringInputUI extends Component { const text = toUser(recentSearch); const start = 0; const end = query.length; - return { type: autocomplete.QuerySuggestionsTypes.RecentSearch, text, start, end }; + return { type: QuerySuggestionTypes.RecentSearch, text, start, end }; }); }; @@ -317,7 +318,7 @@ export class QueryStringInputUI extends Component { } }; - private selectSuggestion = (suggestion: autocomplete.QuerySuggestion) => { + private selectSuggestion = (suggestion: QuerySuggestion) => { if (!this.inputRef) { return; } @@ -341,13 +342,13 @@ export class QueryStringInputUI extends Component { selectionEnd: start + (cursorIndex ? cursorIndex : text.length), }); - if (type === autocomplete.QuerySuggestionsTypes.RecentSearch) { + if (type === QuerySuggestionTypes.RecentSearch) { this.setState({ isSuggestionsVisible: false, index: null }); this.onSubmit({ query: newQueryString, language: this.props.query.language }); } }; - private handleNestedFieldSyntaxNotification = (suggestion: autocomplete.QuerySuggestion) => { + private handleNestedFieldSyntaxNotification = (suggestion: QuerySuggestion) => { if ( 'field' in suggestion && suggestion.field.subType && @@ -449,7 +450,7 @@ export class QueryStringInputUI extends Component { } }; - private onClickSuggestion = (suggestion: autocomplete.QuerySuggestion) => { + private onClickSuggestion = (suggestion: QuerySuggestion) => { if (!this.inputRef) { return; } diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index ba92be8947ea5..9fe33b003527e 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -19,19 +19,19 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { autocomplete } from '../..'; +import { QuerySuggestion, QuerySuggestionTypes } from '../../autocomplete'; import { SuggestionComponent } from './suggestion_component'; const noop = () => { return; }; -const mockSuggestion: autocomplete.QuerySuggestion = { +const mockSuggestion: QuerySuggestion = { description: 'This is not a helpful suggestion', end: 0, start: 42, text: 'as promised, not helpful', - type: autocomplete.QuerySuggestionsTypes.Value, + type: QuerySuggestionTypes.Value, }; describe('SuggestionComponent', () => { diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx index 1d2ac8dee1a8a..4c46c4f802e6a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -20,7 +20,7 @@ import { EuiIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { autocomplete } from '../..'; +import { QuerySuggestion } from '../../autocomplete'; function getEuiIconType(type: string) { switch (type) { @@ -40,10 +40,10 @@ function getEuiIconType(type: string) { } interface Props { - onClick: (suggestion: autocomplete.QuerySuggestion) => void; + onClick: (suggestion: QuerySuggestion) => void; onMouseEnter: () => void; selected: boolean; - suggestion: autocomplete.QuerySuggestion; + suggestion: QuerySuggestion; innerRef: (node: HTMLDivElement) => void; ariaId: string; } diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index eebe438025949..b26582810ad4a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { autocomplete } from '../..'; +import { QuerySuggestion, QuerySuggestionTypes } from '../../autocomplete'; import { SuggestionComponent } from './suggestion_component'; import { SuggestionsComponent } from './suggestions_component'; @@ -27,20 +27,20 @@ const noop = () => { return; }; -const mockSuggestions: autocomplete.QuerySuggestion[] = [ +const mockSuggestions: QuerySuggestion[] = [ { description: 'This is not a helpful suggestion', end: 0, start: 42, text: 'as promised, not helpful', - type: autocomplete.QuerySuggestionsTypes.Value, + type: QuerySuggestionTypes.Value, }, { description: 'This is another unhelpful suggestion', end: 0, start: 42, text: 'yep', - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, }, ]; diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index b37a2e479e874..375bc63a2318c 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,15 +19,15 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; -import { autocomplete } from '../..'; +import { QuerySuggestion } from '../..'; import { SuggestionComponent } from './suggestion_component'; interface Props { index: number | null; - onClick: (suggestion: autocomplete.QuerySuggestion) => void; + onClick: (suggestion: QuerySuggestion) => void; onMouseEnter: (index: number) => void; show: boolean; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; loadMore: () => void; } diff --git a/src/plugins/data/server/field_formats/field_formats_service.ts b/src/plugins/data/server/field_formats/field_formats_service.ts index 923904db9def0..a31e5927ab800 100644 --- a/src/plugins/data/server/field_formats/field_formats_service.ts +++ b/src/plugins/data/server/field_formats/field_formats_service.ts @@ -17,16 +17,15 @@ * under the License. */ import { has } from 'lodash'; -import { fieldFormats } from '../../common/field_formats'; +import { FieldFormatsRegistry, IFieldFormatType, baseFormatters } from '../../common/field_formats'; import { IUiSettingsClient } from '../../../../core/server'; export class FieldFormatsService { - private readonly fieldFormatClasses: fieldFormats.IFieldFormatType[] = - fieldFormats.baseFormatters; + private readonly fieldFormatClasses: IFieldFormatType[] = baseFormatters; public setup() { return { - register: (customFieldFormat: fieldFormats.IFieldFormatType) => + register: (customFieldFormat: IFieldFormatType) => this.fieldFormatClasses.push(customFieldFormat), }; } @@ -34,7 +33,7 @@ export class FieldFormatsService { public start() { return { fieldFormatServiceFactory: async (uiSettings: IUiSettingsClient) => { - const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); + const fieldFormatsRegistry = new FieldFormatsRegistry(); const uiConfigs = await uiSettings.getAll(); const registeredUiSettings = uiSettings.getRegistered(); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 45bd111a2ce4f..1dc8528dbba67 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -20,6 +20,50 @@ import { PluginInitializerContext } from '../../../core/server'; import { DataServerPlugin, DataPluginSetup, DataPluginStart } from './plugin'; +/* + * Field Formatters helper namespace: + */ + +import { + FieldFormat, + FieldFormatsRegistry, // exported only for tests. Consider mock. + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + UrlFormat, + StringFormat, + TruncateFormat, +} from '../common/field_formats'; + +export const fieldFormats = { + FieldFormat, + FieldFormatsRegistry, // exported only for tests. Consider mock. + + BoolFormat, + BytesFormat, + ColorFormat, + DateFormat, + DateNanosFormat, + DurationFormat, + IpFormat, + NumberFormat, + PercentFormat, + RelativeDateFormat, + SourceFormat, + StaticLookupFormat, + UrlFormat, + StringFormat, + TruncateFormat, +}; export function plugin(initializerContext: PluginInitializerContext) { return new DataServerPlugin(initializerContext); } @@ -35,7 +79,6 @@ export { esFilters, esKuery, esQuery, - fieldFormats, // kbn field types castEsToKbnFieldTypeName, getKbnFieldType, @@ -56,6 +99,9 @@ export { // utils parseInterval, isNestedField, + IFieldFormatsRegistry, + FieldFormatsGetConfigFn, + FieldFormatConfig, } from '../common'; /** diff --git a/src/legacy/ui/public/registry/feature_catalogue.d.ts b/src/plugins/home/public/mocks/index.ts similarity index 54% rename from src/legacy/ui/public/registry/feature_catalogue.d.ts rename to src/plugins/home/public/mocks/index.ts index 031c3efa6c5ad..dead50230ec85 100644 --- a/src/legacy/ui/public/registry/feature_catalogue.d.ts +++ b/src/plugins/home/public/mocks/index.ts @@ -17,26 +17,22 @@ * under the License. */ -import { I18nServiceType } from '@kbn/i18n/angular'; +import { featureCatalogueRegistryMock } from '../services/feature_catalogue/feature_catalogue_registry.mock'; +import { environmentServiceMock } from '../services/environment/environment.mock'; +import { configSchema } from '../../config'; -export enum FeatureCatalogueCategory { - ADMIN = 'admin', - DATA = 'data', - OTHER = 'other', -} +const createSetupContract = () => ({ + featureCatalogue: featureCatalogueRegistryMock.createSetup(), + environment: environmentServiceMock.createSetup(), + config: configSchema.validate({}), +}); -interface FeatureCatalogueObject { - id: string; - title: string; - description: string; - icon: string; - path: string; - showOnHomePage: boolean; - category: FeatureCatalogueCategory; -} +const createStartContract = () => ({ + featureCatalogue: featureCatalogueRegistryMock.createStart(), + environment: environmentServiceMock.createStart(), +}); -type FeatureCatalogueRegistryFunction = (i18n: I18nServiceType) => FeatureCatalogueObject; - -export const FeatureCatalogueRegistryProvider: { - register: (fn: FeatureCatalogueRegistryFunction) => void; +export const homePluginMock = { + createSetupContract, + createStartContract, }; diff --git a/src/plugins/kibana_legacy/public/angular_bootstrap/bind_html/bind_html.js b/src/plugins/kibana_legacy/public/angular_bootstrap/bind_html/bind_html.js new file mode 100755 index 0000000000000..77844a3dd1363 --- /dev/null +++ b/src/plugins/kibana_legacy/public/angular_bootstrap/bind_html/bind_html.js @@ -0,0 +1,17 @@ +/* eslint-disable */ + +import angular from 'angular'; + +export function initBindHtml() { + angular + .module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function() { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +} diff --git a/src/plugins/kibana_legacy/public/angular_bootstrap/index.ts b/src/plugins/kibana_legacy/public/angular_bootstrap/index.ts new file mode 100644 index 0000000000000..1f15107a02762 --- /dev/null +++ b/src/plugins/kibana_legacy/public/angular_bootstrap/index.ts @@ -0,0 +1,50 @@ +/* eslint-disable */ + +import { once } from 'lodash'; +import angular from 'angular'; + +// @ts-ignore +import { initBindHtml } from './bind_html/bind_html'; +// @ts-ignore +import { initBootstrapTooltip } from './tooltip/tooltip'; + +import tooltipPopup from './tooltip/tooltip_popup.html'; + +import tooltipUnsafePopup from './tooltip/tooltip_html_unsafe_popup.html'; + +export const initAngularBootstrap = once(() => { + /* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.12.1 - 2015-02-20 + * License: MIT + */ + angular.module('ui.bootstrap', [ + 'ui.bootstrap.tpls', + 'ui.bootstrap.bindHtml', + 'ui.bootstrap.tooltip', + ]); + + angular.module('ui.bootstrap.tpls', [ + 'template/tooltip/tooltip-html-unsafe-popup.html', + 'template/tooltip/tooltip-popup.html', + ]); + + initBindHtml(); + initBootstrapTooltip(); + + angular.module('template/tooltip/tooltip-html-unsafe-popup.html', []).run([ + '$templateCache', + function($templateCache: any) { + $templateCache.put('template/tooltip/tooltip-html-unsafe-popup.html', tooltipUnsafePopup); + }, + ]); + + angular.module('template/tooltip/tooltip-popup.html', []).run([ + '$templateCache', + function($templateCache: any) { + $templateCache.put('template/tooltip/tooltip-popup.html', tooltipPopup); + }, + ]); +}); diff --git a/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/position.js b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/position.js new file mode 100755 index 0000000000000..24c8a8c5979cd --- /dev/null +++ b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/position.js @@ -0,0 +1,167 @@ +/* eslint-disable */ + +import angular from 'angular'; + +export function initBootstrapPosition() { + angular + .module('ui.bootstrap.position', []) + + /** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', [ + '$document', + '$window', + function($document, $window) { + function getStyle(el, cssprop) { + if (el.currentStyle) { + //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static') === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + const parentOffsetEl = function(element) { + const docDomEl = $document[0]; + let offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function(element) { + const elBCR = this.offset(element); + let offsetParentBCR = { top: 0, left: 0 }; + const offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + const boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left, + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function(element) { + const boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: + boundingClientRect.top + + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: + boundingClientRect.left + + ($window.pageXOffset || $document[0].documentElement.scrollLeft), + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function(hostEl, targetEl, positionStr, appendToBody) { + const positionStrParts = positionStr.split('-'); + const pos0 = positionStrParts[0]; + const pos1 = positionStrParts[1] || 'center'; + + let hostElPos; + let targetElWidth; + let targetElHeight; + let targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + const shiftWidth = { + center: function() { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function() { + return hostElPos.left; + }, + right: function() { + return hostElPos.left + hostElPos.width; + }, + }; + + const shiftHeight = { + center: function() { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function() { + return hostElPos.top; + }, + bottom: function() { + return hostElPos.top + hostElPos.height; + }, + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0](), + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth, + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1](), + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1](), + }; + break; + } + + return targetElPos; + }, + }; + }, + ]); +} diff --git a/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip.js b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip.js new file mode 100755 index 0000000000000..05235fde9419b --- /dev/null +++ b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip.js @@ -0,0 +1,423 @@ +/* eslint-disable */ + +import angular from 'angular'; + +import { initBootstrapPosition } from './position'; + +export function initBootstrapTooltip() { + initBootstrapPosition(); + /** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ + angular + .module('ui.bootstrap.tooltip', ['ui.bootstrap.position']) + + /** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ + .provider('$tooltip', function() { + // The default options tooltip and popover. + const defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0, + }; + + // Default hide triggers for each show trigger + const triggerMap = { + mouseenter: 'mouseleave', + click: 'click', + focus: 'blur', + }; + + // The options specified to the provider globally. + const globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function(value) { + angular.extend(globalOptions, value); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name) { + const regexp = /[A-Z]/g; + const separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ + '$window', + '$compile', + '$timeout', + '$document', + '$position', + '$interpolate', + function($window, $compile, $timeout, $document, $position, $interpolate) { + return function $tooltip(type, prefix, defaultTriggerShow) { + const options = angular.extend({}, defaultOptions, globalOptions); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers(trigger) { + const show = trigger || options.trigger || defaultTriggerShow; + const hide = triggerMap[show] || show; + return { + show: show, + hide: hide, + }; + } + + const directiveName = snake_case(type); + + const startSym = $interpolate.startSymbol(); + const endSym = $interpolate.endSymbol(); + const template = + '
' + + '
'; + + return { + restrict: 'EA', + compile: function(tElem, tAttrs) { + const tooltipLinker = $compile(template); + + return function link(scope, element, attrs) { + let tooltip; + let tooltipLinkedScope; + let transitionTimeout; + let popupTimeout; + let appendToBody = angular.isDefined(options.appendToBody) + ? options.appendToBody + : false; + let triggers = getTriggers(undefined); + const hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); + let ttScope = scope.$new(true); + + const positionTooltip = function() { + const ttPosition = $position.positionElements( + element, + tooltip, + ttScope.placement, + appendToBody + ); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css(ttPosition); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + ttScope.isOpen = false; + + function toggleTooltipBind() { + if (!ttScope.isOpen) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { + return; + } + + prepareTooltip(); + + if (ttScope.popupDelay) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout(show, ttScope.popupDelay, false); + popupTimeout + .then(reposition => reposition()) + .catch(error => { + // if the timeout is canceled then the string `canceled` is thrown. To prevent + // this from triggering an 'unhandled promise rejection' in angular 1.5+ the + // $timeout service explicitly tells $q that the promise it generated is "handled" + // but that does not include down chain promises like the one created by calling + // `popupTimeout.then()`. Because of this we need to ignore the "canceled" string + // and only propagate real errors + if (error !== 'canceled') { + throw error; + } + }); + } + } else { + show()(); + } + } + + function hideTooltipBind() { + scope.$evalAsync(function() { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if (!ttScope.content) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + ttScope.$digest(); + + positionTooltip(); + + // And show the tooltip. + ttScope.isOpen = true; + ttScope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + ttScope.isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel(popupTimeout); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltipLinkedScope = ttScope.$new(); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); + } else { + element.after(tooltip); + } + }); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + if (tooltipLinkedScope) { + tooltipLinkedScope.$destroy(); + tooltipLinkedScope = null; + } + } + + function prepareTooltip() { + prepPlacement(); + prepPopupDelay(); + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe(type, function(val) { + ttScope.content = val; + + if (!val && ttScope.isOpen) { + hide(); + } + }); + + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + }); + + function prepPlacement() { + const val = attrs[prefix + 'Placement']; + ttScope.placement = angular.isDefined(val) ? val : options.placement; + } + + function prepPopupDelay() { + const val = attrs[prefix + 'PopupDelay']; + const delay = parseInt(val, 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + } + + const unregisterTriggers = function() { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + function prepTriggers() { + const val = attrs[prefix + 'Trigger']; + unregisterTriggers(); + + triggers = getTriggers(val); + + if (triggers.show === triggers.hide) { + element.bind(triggers.show, toggleTooltipBind); + } else { + element.bind(triggers.show, showTooltipBind); + element.bind(triggers.hide, hideTooltipBind); + } + } + + prepTriggers(); + + const animation = scope.$eval(attrs[prefix + 'Animation']); + ttScope.animation = angular.isDefined(animation) + ? !!animation + : options.animation; + + const appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); + appendToBody = angular.isDefined(appendToBodyVal) + ? appendToBodyVal + : appendToBody; + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if (appendToBody) { + scope.$on( + '$locationChangeSuccess', + function closeTooltipOnLocationChangeSuccess() { + if (ttScope.isOpen) { + hide(); + } + } + ); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel(transitionTimeout); + $timeout.cancel(popupTimeout); + unregisterTriggers(); + removeTooltip(); + ttScope = null; + }); + }; + }, + }; + }; + }, + ]; + }) + + .directive('tooltip', [ + '$tooltip', + function($tooltip) { + return $tooltip('tooltip', 'tooltip', 'mouseenter'); + }, + ]) + + .directive('tooltipPopup', function() { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + }; + }) + + .directive('tooltipHtmlUnsafe', [ + '$tooltip', + function($tooltip) { + return $tooltip('tooltipHtmlUnsafe', 'tooltip', 'mouseenter'); + }, + ]) + + .directive('tooltipHtmlUnsafePopup', function() { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html', + }; + }); +} diff --git a/src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html similarity index 100% rename from src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html rename to src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html diff --git a/src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html b/src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html similarity index 100% rename from src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html rename to src/plugins/kibana_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js b/src/plugins/kibana_legacy/public/dashboard_config.ts similarity index 69% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js rename to src/plugins/kibana_legacy/public/dashboard_config.ts index aa8333a1bafca..3c7670682ce25 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_config.js +++ b/src/plugins/kibana_legacy/public/dashboard_config.ts @@ -17,11 +17,13 @@ * under the License. */ -import { uiModules } from 'ui/modules'; -import { capabilities } from 'ui/capabilities'; +export interface DashboardConfig { + turnHideWriteControlsOn(): void; + getHideWriteControls(): boolean; +} -export function dashboardConfigProvider() { - let hideWriteControls = !capabilities.get().dashboard.showWriteControls; +export function getDashboardConfig(hideWriteControls: boolean): DashboardConfig { + let _hideWriteControls = hideWriteControls; return { /** @@ -29,16 +31,10 @@ export function dashboardConfigProvider() { * @type {boolean} */ turnHideWriteControlsOn() { - hideWriteControls = true; + _hideWriteControls = true; }, - $get() { - return { - getHideWriteControls() { - return hideWriteControls; - }, - }; + getHideWriteControls() { + return _hideWriteControls; }, }; } - -uiModules.get('kibana').provider('dashboardConfig', dashboardConfigProvider); diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts index 6e7a3cf87b87c..19833d638fe4c 100644 --- a/src/plugins/kibana_legacy/public/index.ts +++ b/src/plugins/kibana_legacy/public/index.ts @@ -24,6 +24,8 @@ export const plugin = (initializerContext: PluginInitializerContext) => new KibanaLegacyPlugin(initializerContext); export * from './plugin'; + +export { initAngularBootstrap } from './angular_bootstrap'; export * from './angular'; export * from './notify'; export * from './utils'; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts index b6287dd9d9a55..aab3ab315f0c6 100644 --- a/src/plugins/kibana_legacy/public/mocks.ts +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -36,6 +36,10 @@ const createStartContract = (): Start => ({ config: { defaultAppId: 'home', }, + dashboardConfig: { + turnHideWriteControlsOn: jest.fn(), + getHideWriteControls: jest.fn(), + }, }); export const kibanaLegacyPluginMock = { diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index 7c4b3428cbb6d..86e56c44646c0 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -17,9 +17,16 @@ * under the License. */ -import { App, AppBase, PluginInitializerContext, AppUpdatableFields } from 'kibana/public'; +import { + App, + AppBase, + PluginInitializerContext, + AppUpdatableFields, + CoreStart, +} from 'kibana/public'; import { Observable } from 'rxjs'; import { ConfigSchema } from '../config'; +import { getDashboardConfig } from './dashboard_config'; interface ForwardDefinition { legacyAppId: string; @@ -104,7 +111,7 @@ export class KibanaLegacyPlugin { }; } - public start() { + public start({ application }: CoreStart) { return { /** * @deprecated @@ -117,6 +124,7 @@ export class KibanaLegacyPlugin { */ getForwards: () => this.forwards, config: this.initializerContext.config.get(), + dashboardConfig: getDashboardConfig(!application.capabilities.dashboard.showWriteControls), }; } } diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index ca8ad6410eec3..4d0fe8364a66c 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -28,7 +28,7 @@ export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted - renameFromRoot('kibana.defaultAppId', 'kibanaLegacy.defaultAppId', true), + renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), ], }; diff --git a/src/plugins/management/kibana.json b/src/plugins/management/kibana.json index 4bbf2039c8f38..1789b7cd5ddba 100644 --- a/src/plugins/management/kibana.json +++ b/src/plugins/management/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["kibanaLegacy"] + "requiredPlugins": ["kibanaLegacy", "home"] } diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index cc56928e8e529..6099a2cc32afc 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -17,10 +17,26 @@ * under the License. */ -const createStartContract = () => ({ +import { ManagementSetup, ManagementStart } from '../types'; + +const createSetupContract = (): DeeplyMockedKeys => ({ + sections: { + register: jest.fn(), + getSection: jest.fn(), + getAllSections: jest.fn(), + }, +}); + +const createStartContract = (): DeeplyMockedKeys => ({ legacy: {}, + sections: { + getSection: jest.fn(), + getAllSections: jest.fn(), + navigateToApp: jest.fn(), + }, }); export const managementPluginMock = { + createSetupContract, createStartContract, }; diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index ce6959ec31345..df2398412dac2 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -17,10 +17,12 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { ManagementSetup, ManagementStart } from './types'; import { ManagementService } from './management_service'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public'; // @ts-ignore import { LegacyManagementAdapter } from './legacy'; @@ -28,7 +30,24 @@ export class ManagementPlugin implements Plugin = {}) { } describe('create()', () => { - test('creates an alert', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ + let alertsClient: AlertsClient; + + beforeEach(() => { + alertsClient = new AlertsClient(alertsClientParams); + alertTypeRegistry.get.mockReturnValue({ id: '123', name: 'Test', actionGroups: ['default'], async executor() {}, }); + }); + + test('creates an alert', async () => { + const data = getMockData(); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -263,7 +268,6 @@ describe('create()', () => { }); test('creates an alert with multiple actions', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData({ actions: [ { @@ -289,12 +293,6 @@ describe('create()', () => { }, ], }); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -446,14 +444,7 @@ describe('create()', () => { }); test('creates a disabled alert', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData({ enabled: false }); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -527,9 +518,8 @@ describe('create()', () => { }); test('should validate params', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ + alertTypeRegistry.get.mockReturnValue({ id: '123', name: 'Test', actionGroups: [], @@ -547,14 +537,7 @@ describe('create()', () => { }); test('throws error if loading actions fails', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockRejectedValueOnce(new Error('Test Error')); await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( `"Test Error"` @@ -564,14 +547,7 @@ describe('create()', () => { }); test('throws error if create saved object fails', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -592,14 +568,7 @@ describe('create()', () => { }); test('attempts to remove saved object if scheduling failed', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -655,14 +624,7 @@ describe('create()', () => { }); test('returns task manager error if cleanup fails, logs to console', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [ { @@ -714,7 +676,6 @@ describe('create()', () => { }); test('throws an error if alert type not registerd', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); alertTypeRegistry.get.mockImplementation(() => { throw new Error('Invalid type'); @@ -725,14 +686,7 @@ describe('create()', () => { }); test('calls the API key function', async () => { - const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); - alertTypeRegistry.get.mockReturnValueOnce({ - id: '123', - name: 'Test', - actionGroups: ['default'], - async executor() {}, - }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, result: { id: '123', api_key: 'abc' }, @@ -845,23 +799,141 @@ describe('create()', () => { } ); }); -}); -describe('enable()', () => { - test('enables an alert', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + test(`doesn't create API key for disabled alerts`, async () => { + const data = getMockData({ enabled: false }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); + savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { + alertTypeId: '123', schedule: { interval: '10s' }, - alertTypeId: '2', - enabled: false, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], }, - version: '123', - references: [], + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], }); taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + scheduledTaskId: 'task-123', + }, + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + }); + await alertsClient.create({ data }); + + expect(alertsClientParams.createAPIKey).not.toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalledWith( + 'alert', + { + actions: [ + { + actionRef: 'action_0', + group: 'default', + actionTypeId: 'test', + params: { foo: true }, + }, + ], + alertTypeId: '123', + consumer: 'bar', + name: 'abc', + params: { bar: true }, + apiKey: null, + apiKeyOwner: null, + createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + enabled: false, + schedule: { interval: '10s' }, + throttle: null, + muteAll: false, + mutedInstanceIds: [], + tags: ['foo'], + }, + { + references: [ + { + id: '1', + name: 'action_0', + type: 'action', + }, + ], + } + ); + }); +}); + +describe('enable()', () => { + let alertsClient: AlertsClient; + const existingAlert = { + id: '1', + type: 'alert', + attributes: { + schedule: { interval: '10s' }, + alertTypeId: '2', + enabled: false, + }, + version: '123', + references: [], + }; + + beforeEach(() => { + alertsClient = new AlertsClient(alertsClientParams); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingAlert); + savedObjectsClient.get.mockResolvedValue(existingAlert); + alertsClientParams.createAPIKey.mockResolvedValue({ + apiKeysEnabled: false, + }); + taskManager.schedule.mockResolvedValue({ id: 'task-123', scheduledAt: new Date(), attempts: 0, @@ -874,8 +946,16 @@ describe('enable()', () => { retryAt: null, ownerId: null, }); + }); + test('enables an alert', async () => { await alertsClient.enable({ id: '1' }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); expect(savedObjectsClient.update).toHaveBeenCalledWith( 'alert', '1', @@ -891,9 +971,6 @@ describe('enable()', () => { version: '123', } ); - expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { - scheduledTaskId: 'task-123', - }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { @@ -907,144 +984,196 @@ describe('enable()', () => { }, scope: ['alerting'], }); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); + }); + + test('invalidates API key if ever one existed prior to updating', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({ + ...existingAlert, + attributes: { + ...existingAlert.attributes, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }); + + await alertsClient.enable({ id: '1' }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); }); test(`doesn't enable already enabled alerts`, async () => { - const alertsClient = new AlertsClient(alertsClientParams); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', + ...existingAlert, attributes: { + ...existingAlert.attributes, + enabled: true, + }, + }); + + await alertsClient.enable({ id: '1' }); + expect(alertsClientParams.getUserName).not.toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).not.toHaveBeenCalled(); + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('sets API key when createAPIKey returns one', async () => { + alertsClientParams.createAPIKey.mockResolvedValueOnce({ + apiKeysEnabled: true, + result: { id: '123', api_key: 'abc' }, + }); + + await alertsClient.enable({ id: '1' }); + expect(savedObjectsClient.update).toHaveBeenCalledWith( + 'alert', + '1', + { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, + apiKey: Buffer.from('123:abc').toString('base64'), + apiKeyOwner: 'elastic', + updatedBy: 'elastic', + }, + { + version: '123', + } + ); + }); + + test('falls back when failing to getDecryptedAsInternalUser', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValue(new Error('Fail')); + + await alertsClient.enable({ id: '1' }); + expect(savedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'enable(): Failed to load API key to invalidate on alert 1: Fail' + ); + }); + + test('throws error when failing to load the saved object using SOC', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValue(new Error('Fail')); + savedObjectsClient.get.mockRejectedValueOnce(new Error('Fail to get')); + + await expect(alertsClient.enable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Fail to get"` + ); + expect(alertsClientParams.getUserName).not.toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).not.toHaveBeenCalled(); + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('throws error when failing to update the first time', async () => { + savedObjectsClient.update.mockRejectedValueOnce(new Error('Fail to update')); + + await expect(alertsClient.enable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Fail to update"` + ); + expect(alertsClientParams.getUserName).toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('throws error when failing to update the second time', async () => { + savedObjectsClient.update.mockResolvedValueOnce({ + ...existingAlert, + attributes: { + ...existingAlert.attributes, + enabled: true, }, - references: [], }); + savedObjectsClient.update.mockRejectedValueOnce(new Error('Fail to update second time')); + + await expect(alertsClient.enable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Fail to update second time"` + ); + expect(alertsClientParams.getUserName).toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); + expect(taskManager.schedule).toHaveBeenCalled(); + }); + + test('throws error when failing to schedule task', async () => { + taskManager.schedule.mockRejectedValueOnce(new Error('Fail to schedule')); + + await expect(alertsClient.enable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Fail to schedule"` + ); + expect(alertsClientParams.getUserName).toHaveBeenCalled(); + expect(alertsClientParams.createAPIKey).toHaveBeenCalled(); + expect(savedObjectsClient.update).toHaveBeenCalled(); + }); +}); + +describe('disable()', () => { + let alertsClient: AlertsClient; + const existingAlert = { + id: '1', + type: 'alert', + attributes: { + schedule: { interval: '10s' }, + alertTypeId: '2', + enabled: true, + scheduledTaskId: 'task-123', + }, + version: '123', + references: [], + }; + const existingDecryptedAlert = { + ...existingAlert, + attributes: { + ...existingAlert.attributes, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }; - await alertsClient.enable({ id: '1' }); - expect(taskManager.schedule).toHaveBeenCalledTimes(0); - expect(savedObjectsClient.update).toHaveBeenCalledTimes(0); + beforeEach(() => { + alertsClient = new AlertsClient(alertsClientParams); + savedObjectsClient.get.mockResolvedValue(existingAlert); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedAlert); }); - test('calls the API key function', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - schedule: { interval: '10s' }, - alertTypeId: '2', - enabled: false, - }, - version: '123', - references: [], - }); - taskManager.schedule.mockResolvedValueOnce({ - id: 'task-123', - scheduledAt: new Date(), - attempts: 0, - status: TaskStatus.Idle, - runAt: new Date(), - state: {}, - params: {}, - taskType: '', - startedAt: null, - retryAt: null, - ownerId: null, - }); - alertsClientParams.createAPIKey.mockResolvedValueOnce({ - apiKeysEnabled: true, - result: { id: '123', api_key: 'abc' }, + test('disables an alert', async () => { + await alertsClient.disable({ id: '1' }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', }); - - await alertsClient.enable({ id: '1' }); expect(savedObjectsClient.update).toHaveBeenCalledWith( 'alert', '1', { schedule: { interval: '10s' }, alertTypeId: '2', - enabled: true, - apiKey: Buffer.from('123:abc').toString('base64'), - apiKeyOwner: 'elastic', + apiKey: null, + apiKeyOwner: null, + enabled: false, + scheduledTaskId: null, updatedBy: 'elastic', }, { version: '123', } ); - expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { - scheduledTaskId: 'task-123', - }); - expect(taskManager.schedule).toHaveBeenCalledWith({ - taskType: `alerting:2`, - params: { - alertId: '1', - spaceId: 'default', - }, - state: { - alertInstances: {}, - alertTypeState: {}, - previousStartedAt: null, - }, - scope: ['alerting'], - }); - }); - - test('swallows error when invalidate API key throws', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - schedule: { interval: '10s' }, - alertTypeId: '2', - enabled: false, - apiKey: Buffer.from('123:abc').toString('base64'), - }, - version: '123', - references: [], - }); - taskManager.schedule.mockResolvedValueOnce({ - id: 'task-123', - scheduledAt: new Date(), - attempts: 0, - status: TaskStatus.Idle, - runAt: new Date(), - state: {}, - params: {}, - taskType: '', - startedAt: null, - retryAt: null, - ownerId: null, - }); - - await alertsClient.enable({ id: '1' }); - expect(alertsClientParams.logger.error).toHaveBeenCalledWith( - 'Failed to invalidate API Key: Fail' - ); + expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); }); -}); -describe('disable()', () => { - test('disables an alert', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - schedule: { interval: '10s' }, - alertTypeId: '2', - enabled: true, - scheduledTaskId: 'task-123', - }, - version: '123', - references: [], - }); + test('falls back when getDecryptedAsInternalUser throws an error', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); await alertsClient.disable({ id: '1' }); + expect(savedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); expect(savedObjectsClient.update).toHaveBeenCalledWith( 'alert', '1', @@ -1062,25 +1191,66 @@ describe('disable()', () => { } ); expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); }); test(`doesn't disable already disabled alerts`, async () => { - const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ - id: '1', - type: 'alert', + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + ...existingDecryptedAlert, attributes: { - schedule: { interval: '10s' }, - alertTypeId: '2', + ...existingDecryptedAlert.attributes, enabled: false, - scheduledTaskId: 'task-123', }, - references: [], }); await alertsClient.disable({ id: '1' }); - expect(savedObjectsClient.update).toHaveBeenCalledTimes(0); - expect(taskManager.remove).toHaveBeenCalledTimes(0); + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + expect(taskManager.remove).not.toHaveBeenCalled(); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + }); + + test(`doesn't invalidate when no API key is used`, async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce(existingAlert); + + await alertsClient.disable({ id: '1' }); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + }); + + test('swallows error when failing to load decrypted saved object', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + + await alertsClient.disable({ id: '1' }); + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(taskManager.remove).toHaveBeenCalled(); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'disable(): Failed to load API key to invalidate on alert 1: Fail' + ); + }); + + test('throws when savedObjectsClient update fails', async () => { + savedObjectsClient.update.mockRejectedValueOnce(new Error('Failed to update')); + + await expect(alertsClient.disable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to update"` + ); + }); + + test('swallows error when invalidate API key throws', async () => { + alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); + + await alertsClient.disable({ id: '1' }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'Failed to invalidate API Key: Fail' + ); + }); + + test('throws when failing to remove task from task manager', async () => { + taskManager.remove.mockRejectedValueOnce(new Error('Failed to remove task')); + + await expect(alertsClient.disable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to remove task"` + ); }); }); @@ -2544,25 +2714,42 @@ describe('update()', () => { }); describe('updateApiKey()', () => { - test('updates the API key for the alert', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - schedule: { interval: '10s' }, - alertTypeId: '2', - enabled: true, - }, - version: '123', - references: [], - }); + let alertsClient: AlertsClient; + const existingAlert = { + id: '1', + type: 'alert', + attributes: { + schedule: { interval: '10s' }, + alertTypeId: '2', + enabled: true, + }, + version: '123', + references: [], + }; + const existingEncryptedAlert = { + ...existingAlert, + attributes: { + ...existingAlert.attributes, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }; + + beforeEach(() => { + alertsClient = new AlertsClient(alertsClientParams); + savedObjectsClient.get.mockResolvedValue(existingAlert); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingEncryptedAlert); alertsClientParams.createAPIKey.mockResolvedValueOnce({ apiKeysEnabled: true, - result: { id: '123', api_key: 'abc' }, + result: { id: '234', api_key: 'abc' }, }); + }); + test('updates the API key for the alert', async () => { await alertsClient.updateApiKey({ id: '1' }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); expect(savedObjectsClient.update).toHaveBeenCalledWith( 'alert', '1', @@ -2570,37 +2757,66 @@ describe('updateApiKey()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - apiKey: Buffer.from('123:abc').toString('base64'), + apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', }, { version: '123' } ); + expect(alertsClientParams.invalidateAPIKey).toHaveBeenCalledWith({ id: '123' }); }); - test('swallows error when invalidate API key throws', async () => { - const alertsClient = new AlertsClient(alertsClientParams); - alertsClientParams.invalidateAPIKey.mockRejectedValue(new Error('Fail')); - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { + test('falls back to SOC when getDecryptedAsInternalUser throws an error', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + + await alertsClient.updateApiKey({ id: '1' }); + expect(savedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(savedObjectsClient.update).toHaveBeenCalledWith( + 'alert', + '1', + { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - apiKey: Buffer.from('123:abc').toString('base64'), + apiKey: Buffer.from('234:abc').toString('base64'), + apiKeyOwner: 'elastic', + updatedBy: 'elastic', }, - version: '123', - references: [], - }); - alertsClientParams.createAPIKey.mockResolvedValueOnce({ - apiKeysEnabled: true, - result: { id: '123', api_key: 'abc' }, - }); + { version: '123' } + ); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + }); + + test('swallows error when invalidate API key throws', async () => { + alertsClientParams.invalidateAPIKey.mockRejectedValue(new Error('Fail')); await alertsClient.updateApiKey({ id: '1' }); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'Failed to invalidate API Key: Fail' ); + expect(savedObjectsClient.update).toHaveBeenCalled(); + }); + + test('swallows error when getting decrypted object throws', async () => { + encryptedSavedObjects.getDecryptedAsInternalUser.mockRejectedValueOnce(new Error('Fail')); + + await alertsClient.updateApiKey({ id: '1' }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'updateApiKey(): Failed to load API key to invalidate on alert 1: Fail' + ); + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); + }); + + test('throws when savedObjectsClient update fails', async () => { + savedObjectsClient.update.mockRejectedValueOnce(new Error('Fail')); + + await expect(alertsClient.updateApiKey({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Fail"` + ); + expect(alertsClientParams.invalidateAPIKey).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index f6841ed5a0e46..97f556be04957 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -152,13 +152,14 @@ export class AlertsClient { const alertType = this.alertTypeRegistry.get(data.alertTypeId); const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); const username = await this.getUserName(); + const createdAPIKey = data.enabled ? await this.createAPIKey() : null; this.validateActions(alertType, data.actions); const { references, actions } = await this.denormalizeActions(data.actions); const rawAlert: RawAlert = { ...data, - ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), + ...this.apiKeyAsAlertAttributes(createdAPIKey, username), actions, createdBy: username, updatedBy: username, @@ -329,10 +330,10 @@ export class AlertsClient { } private apiKeyAsAlertAttributes( - apiKey: CreateAPIKeyResult, + apiKey: CreateAPIKeyResult | null, username: string | null ): Pick { - return apiKey.apiKeysEnabled + return apiKey && apiKey.apiKeysEnabled ? { apiKeyOwner: username, apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), @@ -344,12 +345,27 @@ export class AlertsClient { } public async updateApiKey({ id }: { id: string }) { - const { - version, - attributes, - } = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); + let apiKeyToInvalidate: string | null = null; + let attributes: RawAlert; + let version: string | undefined; + + try { + const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + RawAlert + >('alert', id, { namespace: this.namespace }); + apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + this.logger.error( + `updateApiKey(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the attributes and version using SOC + const alert = await this.savedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } const username = await this.getUserName(); await this.savedObjectsClient.update( @@ -363,7 +379,9 @@ export class AlertsClient { { version } ); - await this.invalidateApiKey({ apiKey: attributes.apiKey }); + if (apiKeyToInvalidate) { + await this.invalidateApiKey({ apiKey: apiKeyToInvalidate }); + } } private async invalidateApiKey({ apiKey }: { apiKey: string | null }): Promise { @@ -385,12 +403,27 @@ export class AlertsClient { } public async enable({ id }: { id: string }) { - const { - version, - attributes, - } = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); + let apiKeyToInvalidate: string | null = null; + let attributes: RawAlert; + let version: string | undefined; + + try { + const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + RawAlert + >('alert', id, { namespace: this.namespace }); + apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + this.logger.error( + `enable(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the attributes and version using SOC + const alert = await this.savedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } if (attributes.enabled === false) { const username = await this.getUserName(); @@ -407,12 +440,35 @@ export class AlertsClient { ); const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); await this.savedObjectsClient.update('alert', id, { scheduledTaskId: scheduledTask.id }); - await this.invalidateApiKey({ apiKey: attributes.apiKey }); + if (apiKeyToInvalidate) { + await this.invalidateApiKey({ apiKey: apiKeyToInvalidate }); + } } } public async disable({ id }: { id: string }) { - const { attributes, version } = await this.savedObjectsClient.get('alert', id); + let apiKeyToInvalidate: string | null = null; + let attributes: RawAlert; + let version: string | undefined; + + try { + const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + RawAlert + >('alert', id, { namespace: this.namespace }); + apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + this.logger.error( + `disable(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the attributes and version using SOC + const alert = await this.savedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } + if (attributes.enabled === true) { await this.savedObjectsClient.update( 'alert', @@ -427,7 +483,11 @@ export class AlertsClient { }, { version } ); - await this.taskManager.remove(attributes.scheduledTaskId); + + await Promise.all([ + attributes.scheduledTaskId ? this.taskManager.remove(attributes.scheduledTaskId) : null, + apiKeyToInvalidate ? this.invalidateApiKey({ apiKey: apiKeyToInvalidate }) : null, + ]); } } diff --git a/x-pack/legacy/plugins/alerting/server/lib/types.test.ts b/x-pack/legacy/plugins/alerting/server/lib/types.test.ts new file mode 100644 index 0000000000000..517b66aa2faab --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/types.test.ts @@ -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. + */ + +import { DateFromString } from './types'; +import { right, isLeft } from 'fp-ts/lib/Either'; + +describe('DateFromString', () => { + test('validated and parses a string into a Date', () => { + const date = new Date(1973, 10, 30); + expect(DateFromString.decode(date.toISOString())).toEqual(right(date)); + }); + + test('validated and returns a failure for an actual Date', () => { + const date = new Date(1973, 10, 30); + expect(isLeft(DateFromString.decode(date))).toEqual(true); + }); + + test('validated and returns a failure for an invalid Date string', () => { + expect(isLeft(DateFromString.decode('1234-23-45'))).toEqual(true); + }); + + test('validated and returns a failure for a null value', () => { + expect(isLeft(DateFromString.decode(null))).toEqual(true); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/types.ts b/x-pack/legacy/plugins/alerting/server/lib/types.ts new file mode 100644 index 0000000000000..6df593ab17ce8 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/types.ts @@ -0,0 +1,25 @@ +/* + * 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 * as t from 'io-ts'; +import { either } from 'fp-ts/lib/Either'; +// represents a Date from an ISO string +export const DateFromString = new t.Type( + 'DateFromString', + // detect the type + (value): value is Date => value instanceof Date, + (valueToDecode, context) => + either.chain( + // validate this is a string + t.string.validate(valueToDecode, context), + // decode + value => { + const decoded = new Date(value); + return isNaN(decoded.getTime()) ? t.failure(valueToDecode, context) : t.success(decoded); + } + ), + valueToEncode => valueToEncode.toISOString() +); diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/alert_task_instance.ts b/x-pack/legacy/plugins/alerting/server/task_runner/alert_task_instance.ts index a13dd99d574fb..c800b3b9d70f2 100644 --- a/x-pack/legacy/plugins/alerting/server/task_runner/alert_task_instance.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/alert_task_instance.ts @@ -7,10 +7,10 @@ import * as t from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { ConcreteTaskInstance } from '../../../../../plugins/task_manager/server'; -import { SanitizedAlert } from '../types'; import { DateFromString } from '../../common/date_from_string'; -import { rawAlertInstance } from '../../common'; import { AlertInstance } from '../alert_instance'; +import { SanitizedAlert } from '../../common/alert'; +import { rawAlertInstance } from '../types'; export interface AlertTaskInstance extends ConcreteTaskInstance { state: AlertTaskState; @@ -34,7 +34,7 @@ const alertParamsSchema = t.intersection([ ]); export type AlertTaskParams = t.TypeOf; -const enumarateErrorFields = (e: t.Errors) => +const enumerateErrorFields = (e: t.Errors) => `${e.map(({ context }) => context.map(({ key }) => key).join('.'))}`; export function taskInstanceToAlertTaskInstance( @@ -49,7 +49,7 @@ export function taskInstanceToAlertTaskInstance( throw new Error( `Task "${taskInstance.id}" ${ alert ? `(underlying Alert "${alert.id}") ` : '' - }has an invalid param at ${enumarateErrorFields(e)}` + }has an invalid param at ${enumerateErrorFields(e)}` ); }, t.identity) ), @@ -59,7 +59,7 @@ export function taskInstanceToAlertTaskInstance( throw new Error( `Task "${taskInstance.id}" ${ alert ? `(underlying Alert "${alert.id}") ` : '' - }has invalid state at ${enumarateErrorFields(e)}` + }has invalid state at ${enumerateErrorFields(e)}` ); }, t.identity) ), diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 32432b7b85ef6..5bdc63ab47aa5 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -18,7 +18,7 @@ import { history } from '../../../utils/history'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { - autocomplete, + QuerySuggestion, esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; @@ -28,7 +28,7 @@ const Container = styled.div` `; interface State { - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; isLoadingSuggestions: boolean; } diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx index f3e0f3dfbdae7..70b7bd3df0662 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx @@ -13,7 +13,7 @@ import { import React from 'react'; import styled from 'styled-components'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; import { composeStateUpdaters } from '../../utils/typed_react'; import { SuggestionItem } from './suggestion_item'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx index 0132667b9e510..690d471b306ab 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { tint } from 'polished'; import React from 'react'; import styled from 'styled-components'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: autocomplete.QuerySuggestion; + suggestion: QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx index d1cbc0888dca8..534da6541b683 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eu import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; import { TABLE_CONFIG } from '../../../common/constants'; import { AutocompleteField } from '../autocomplete_field/index'; import { ControlSchema } from './action_schema'; @@ -31,7 +31,7 @@ export interface KueryBarProps { loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; onChange?: (value: string) => void; onSubmit?: (value: string) => void; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx index db73a7cb38c11..66d52b8dcc5dc 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { autocomplete } from '../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../src/plugins/data/public'; import { FrontendLibs } from '../lib/types'; import { RendererFunction } from '../utils/typed_react'; @@ -17,7 +17,7 @@ interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; }>; } @@ -28,7 +28,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts index 12898027d5fb5..6e4665fb130de 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { autocomplete } from '../../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; export interface ElasticsearchAdapter { convertKueryToEsQuery: (kuery: string) => Promise; - getSuggestions: (kuery: string, selectionStart: any) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; isKueryValid(kuery: string): boolean; } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts index 111255b55c99b..fc4daf3df60b2 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { autocomplete } from '../../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapter_types'; export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { constructor( private readonly mockIsKueryValid: (kuery: string) => boolean, private readonly mockKueryToEsQuery: (kuery: string) => string, - private readonly suggestions: autocomplete.QuerySuggestion[] + private readonly suggestions: QuerySuggestion[] ) {} public isKueryValid(kuery: string): boolean { @@ -20,10 +20,7 @@ export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { public async convertKueryToEsQuery(kuery: string): Promise { return this.mockKueryToEsQuery(kuery); } - public async getSuggestions( - kuery: string, - selectionStart: any - ): Promise { + public async getSuggestions(kuery: string, selectionStart: any): Promise { return this.suggestions; } } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index fc400c600e575..06e6fac0d75c4 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; -import { autocomplete, esKuery } from '../../../../../../../../src/plugins/data/public'; +import { QuerySuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; export class RestElasticsearchAdapter implements ElasticsearchAdapter { private cachedIndexPattern: any = null; @@ -31,10 +31,7 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern)); } - public async getSuggestions( - kuery: string, - selectionStart: any - ): Promise { + public async getSuggestions(kuery: string, selectionStart: any): Promise { const indexPattern = await this.getIndexPattern(); return ( diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts index 47df51dea8620..b8ecb644ff1b0 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts @@ -24,14 +24,14 @@ import { TagsLib } from '../tags'; import { FrontendLibs } from '../types'; import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { ElasticsearchLib } from './../elasticsearch'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; const onKibanaReady = uiModules.get('kibana').run; export function compose( mockIsKueryValid: (kuery: string) => boolean, mockKueryToEsQuery: (kuery: string) => string, - suggestions: autocomplete.QuerySuggestion[] + suggestions: QuerySuggestion[] ): FrontendLibs { const esAdapter = new MemoryElasticsearchAdapter( mockIsKueryValid, diff --git a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts index d71512e80d3d5..82576bff2cbfd 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { autocomplete } from '../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; interface HiddenFields { @@ -35,7 +35,7 @@ export class ElasticsearchLib { kuery: string, selectionStart: any, fieldPrefix?: string - ): Promise { + ): Promise { const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); const filteredSuggestions = suggestions.filter(suggestion => { diff --git a/x-pack/legacy/plugins/canvas/i18n/index.ts b/x-pack/legacy/plugins/canvas/i18n/index.ts index a671d0ccdb49f..864311d34aca0 100644 --- a/x-pack/legacy/plugins/canvas/i18n/index.ts +++ b/x-pack/legacy/plugins/canvas/i18n/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; - export * from './capabilities'; export * from './components'; export * from './constants'; @@ -19,8 +17,3 @@ export * from './tags'; export * from './transitions'; export * from './ui'; export * from './units'; - -export const getAppDescription = () => - i18n.translate('xpack.canvas.appDescription', { - defaultMessage: 'Showcase your data in a pixel-perfect way.', - }); diff --git a/x-pack/legacy/plugins/canvas/index.js b/x-pack/legacy/plugins/canvas/index.js index ebd4f35db8175..b357ec9c0b61e 100644 --- a/x-pack/legacy/plugins/canvas/index.js +++ b/x-pack/legacy/plugins/canvas/index.js @@ -36,7 +36,7 @@ export function canvas(kibana) { // window.onerror override 'plugins/canvas/lib/window_error_handler.js', ], - home: ['plugins/canvas/register_feature'], + home: ['plugins/canvas/legacy_register_feature'], mappings, migrations, savedObjectsManagement: { diff --git a/x-pack/legacy/plugins/canvas/public/feature_catalogue_entry.ts b/x-pack/legacy/plugins/canvas/public/feature_catalogue_entry.ts new file mode 100644 index 0000000000000..f610bd0299832 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/feature_catalogue_entry.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +export const featureCatalogueEntry = { + id: 'canvas', + title: 'Canvas', + description: i18n.translate('xpack.canvas.appDescription', { + defaultMessage: 'Showcase your data in a pixel-perfect way.', + }), + icon: 'canvasApp', + path: '/app/canvas', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +}; diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index cbd2aa54627ee..c16bc124747c6 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -22,7 +22,9 @@ const shimCoreSetup = { const shimCoreStart = { ...npStart.core, }; -const shimSetupPlugins = {}; +const shimSetupPlugins = { + home: npSetup.plugins.home, +}; const shimStartPlugins: CanvasStartDeps = { ...npStart.plugins, diff --git a/x-pack/legacy/plugins/canvas/public/legacy_register_feature.ts b/x-pack/legacy/plugins/canvas/public/legacy_register_feature.ts new file mode 100644 index 0000000000000..00f788f267d4b --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/legacy_register_feature.ts @@ -0,0 +1,14 @@ +/* + * 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 { npSetup } from 'ui/new_platform'; +import { featureCatalogueEntry } from './feature_catalogue_entry'; + +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register(featureCatalogueEntry); diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index 7928d46067908..a24fd758808ba 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -10,6 +10,7 @@ import { Chrome } from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { CoreSetup, CoreStart, Plugin } from '../../../../../src/core/public'; +import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; // @ts-ignore: Untyped Local import { CapabilitiesStrings } from '../i18n'; const { ReadOnlyBadge: strings } = CapabilitiesStrings; @@ -27,6 +28,7 @@ import { getDocumentationLinks } from './lib/documentation_links'; // @ts-ignore: untyped local import { initClipboard } from './lib/clipboard'; +import { featureCatalogueEntry } from './feature_catalogue_entry'; export { CoreStart }; /** @@ -34,7 +36,9 @@ export { CoreStart }; * @internal */ // This interface will be built out as we require other plugins for setup -export interface CanvasSetupDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface CanvasSetupDeps { + home: HomePublicPluginSetup; +} export interface CanvasStartDeps { __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; @@ -79,6 +83,9 @@ export class CanvasPlugin return renderApp(coreStart, depsStart, params, canvasStore); }, }); + + plugins.home.featureCatalogue.register(featureCatalogueEntry); + return {}; } diff --git a/x-pack/legacy/plugins/canvas/public/register_feature.js b/x-pack/legacy/plugins/canvas/public/register_feature.js deleted file mode 100644 index 8d78498de34b2..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/register_feature.js +++ /dev/null @@ -1,24 +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 { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { getAppDescription } from '../i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'canvas', - title: 'Canvas', - description: getAppDescription(), - icon: 'canvasApp', - path: '/app/canvas', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js index 1003569733d91..88d8f98b973bd 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -286,7 +286,7 @@ describe('', () => { actions.clickAutoFollowPatternAt(0); find('autoFollowPatternActionMenuButton').simulate('click'); expect(exists('autoFollowPatternDetail.closeFlyoutButton')).toBe(true); - expect(actions.getPatternsActionMenuItemText(0)).toEqual('Resume pattern'); + expect(actions.getPatternsActionMenuItemText(0)).toEqual('Resume replication'); expect(actions.getPatternsActionMenuItemText(1)).toEqual('Edit pattern'); expect(actions.getPatternsActionMenuItemText(2)).toEqual('Delete pattern'); }); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/index.js b/x-pack/legacy/plugins/cross_cluster_replication/index.js index 22e8e73963ccd..1b5f42fc5107e 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/index.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/index.js @@ -9,7 +9,7 @@ import { PLUGIN } from './common/constants'; import { registerLicenseChecker } from './server/lib/register_license_checker'; import { registerRoutes } from './server/routes/register_routes'; import { ccrDataEnricher } from './cross_cluster_replication_data'; -import { addIndexManagementDataEnricher } from '../index_management/server/index_management_data'; + export function crossClusterReplication(kibana) { return new kibana.Plugin({ id: PLUGIN.ID, @@ -49,8 +49,13 @@ export function crossClusterReplication(kibana) { init: function initCcrPlugin(server) { registerLicenseChecker(server); registerRoutes(server); - if (server.config().get('xpack.ccr.ui.enabled')) { - addIndexManagementDataEnricher(ccrDataEnricher); + + if ( + server.config().get('xpack.ccr.ui.enabled') && + server.plugins.index_management && + server.plugins.index_management.addIndexManagementDataEnricher + ) { + server.plugins.index_management.addIndexManagementDataEnricher(ccrDataEnricher); } }, }); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index 7c129eac9cbd9..12654e56bde97 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -68,7 +68,7 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ ? patterns[0].active ? { name: i18n.translate('xpack.crossClusterReplication.pauseAutoFollowPatternsLabel', { - defaultMessage: 'Pause {total, plural, one {pattern} other {patterns}}', + defaultMessage: 'Pause {total, plural, one {replication} other {replications}}', values: { total: patterns.length }, }), icon: , @@ -79,7 +79,7 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ } : { name: i18n.translate('xpack.crossClusterReplication.resumeAutoFollowPatternsLabel', { - defaultMessage: 'Resume {total, plural, one {pattern} other {patterns}}', + defaultMessage: 'Resume {total, plural, one {replication} other {replications}}', values: { total: patterns.length }, }), icon: , diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 08b1770e39963..956a9f10d810b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -180,13 +180,13 @@ export class AutoFollowPatternTable extends PureComponent { ? i18n.translate( 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', { - defaultMessage: 'Pause auto-follow pattern', + defaultMessage: 'Pause replication', } ) : i18n.translate( 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', { - defaultMessage: 'Resume auto-follow pattern', + defaultMessage: 'Resume replication', } ); diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 44c4b81c8ad93..8ca023aa90cf1 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -40,9 +40,7 @@ import { localApplicationService } from 'plugins/kibana/local_application_servic import { showAppRedirectNotification } from 'ui/notify'; import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard'; -uiModules - .get('kibana') - .config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn()); +npStart.plugins.kibanaLegacy.dashboardConfig.turnHideWriteControlsOn(); localApplicationService.attachToAngular(routes); diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index 38a601daa178e..7010e1fa773ea 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -49,6 +49,7 @@ export function initGraphApp(angularModule, deps) { storage, canEditDrillDownUrls, graphSavePolicy, + overlays, } = deps; const app = angularModule; @@ -162,7 +163,7 @@ export function initGraphApp(angularModule, deps) { }); //======== Controller for basic UI ================== - app.controller('graphuiPlugin', function($scope, $route, $location, confirmModal) { + app.controller('graphuiPlugin', function($scope, $route, $location) { function handleError(err) { const toastTitle = i18n.translate('xpack.graph.errorToastTitle', { defaultMessage: 'Graph Error', @@ -382,23 +383,29 @@ export function initGraphApp(angularModule, deps) { return; } const confirmModalOptions = { - onConfirm: callback, - onCancel: () => {}, confirmButtonText: i18n.translate('xpack.graph.leaveWorkspace.confirmButtonLabel', { defaultMessage: 'Leave anyway', }), title: i18n.translate('xpack.graph.leaveWorkspace.modalTitle', { defaultMessage: 'Unsaved changes', }), + 'data-test-subj': 'confirmModal', ...options, }; - confirmModal( - text || - i18n.translate('xpack.graph.leaveWorkspace.confirmText', { - defaultMessage: 'If you leave now, you will lose unsaved changes.', - }), - confirmModalOptions - ); + + overlays + .openConfirm( + text || + i18n.translate('xpack.graph.leaveWorkspace.confirmText', { + defaultMessage: 'If you leave now, you will lose unsaved changes.', + }), + confirmModalOptions + ) + .then(isConfirmed => { + if (isConfirmed) { + callback(); + } + }); } $scope.confirmWipeWorkspace = canWipeWorkspace; diff --git a/x-pack/legacy/plugins/graph/public/application.ts b/x-pack/legacy/plugins/graph/public/application.ts index 8f486ab6ad51a..80a797b7f0724 100644 --- a/x-pack/legacy/plugins/graph/public/application.ts +++ b/x-pack/legacy/plugins/graph/public/application.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiConfirmModal } from '@elastic/eui'; - // inner angular imports // these are necessary to bootstrap the local angular. // They can stay even after NP cutover @@ -20,12 +18,12 @@ import { SavedObjectsClientContract, ToastsStart, IUiSettingsClient, + OverlayStart, } from 'kibana/public'; import { configureAppAngularModule, createTopNavDirective, createTopNavHelper, - confirmModalFactory, addAppRedirectMessageToUrl, } from './legacy_imports'; // @ts-ignore @@ -64,6 +62,7 @@ export interface GraphDependencies { storage: Storage; canEditDrillDownUrls: boolean; graphSavePolicy: string; + overlays: OverlayStart; } export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { @@ -120,24 +119,15 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) { function createLocalAngularModule(navigation: NavigationStart) { createLocalI18nModule(); createLocalTopNavModule(navigation); - createLocalConfirmModalModule(); const graphAngularModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, 'graphI18n', 'graphTopNav', - 'graphConfirmModal', ]); return graphAngularModule; } -function createLocalConfirmModalModule() { - angular - .module('graphConfirmModal', ['react']) - .factory('confirmModal', confirmModalFactory) - .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); -} - function createLocalTopNavModule(navigation: NavigationStart) { angular .module('graphTopNav', ['react']) diff --git a/x-pack/legacy/plugins/graph/public/legacy_imports.ts b/x-pack/legacy/plugins/graph/public/legacy_imports.ts index f1839d62a0667..27184f5701235 100644 --- a/x-pack/legacy/plugins/graph/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/graph/public/legacy_imports.ts @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import 'ui/angular-bootstrap'; import 'ace'; export { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; // @ts-ignore export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; // @ts-ignore -export { confirmModalFactory } from 'ui/modals/confirm_modal'; -// @ts-ignore export { addAppRedirectMessageToUrl } from 'ui/notify'; export { createSavedObjectClass } from 'ui/saved_objects/saved_object'; export { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public'; diff --git a/x-pack/legacy/plugins/graph/public/plugin.ts b/x-pack/legacy/plugins/graph/public/plugin.ts index ab610d76be101..b4ca4bf423181 100644 --- a/x-pack/legacy/plugins/graph/public/plugin.ts +++ b/x-pack/legacy/plugins/graph/public/plugin.ts @@ -10,6 +10,7 @@ import { Plugin as DataPlugin } from 'src/plugins/data/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; +import { initAngularBootstrap } from '../../../../../src/plugins/kibana_legacy/public'; export interface GraphPluginStartDependencies { npData: ReturnType; @@ -26,6 +27,7 @@ export class GraphPlugin implements Plugin { private savedObjectsClient: SavedObjectsClientContract | null = null; setup(core: CoreSetup, { licensing }: GraphPluginSetupDependencies) { + initAngularBootstrap(); core.application.register({ id: 'graph', title: 'Graph', @@ -50,6 +52,7 @@ export class GraphPlugin implements Plugin { config: contextCore.uiSettings, toastNotifications: contextCore.notifications.toasts, indexPatterns: this.npDataStart!.indexPatterns, + overlays: contextCore.overlays, }); }, }); diff --git a/x-pack/legacy/plugins/grokdebugger/public/register_feature.js b/x-pack/legacy/plugins/grokdebugger/public/register_feature.js deleted file mode 100644 index 18021ed0f752d..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/public/register_feature.js +++ /dev/null @@ -1,35 +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 { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'grokdebugger', - title: i18n.translate('xpack.grokDebugger.registryProviderTitle', { - defaultMessage: '{grokLogParsingTool} Debugger', - values: { - grokLogParsingTool: 'Grok', - }, - }), - description: i18n.translate('xpack.grokDebugger.registryProviderDescription', { - defaultMessage: - 'Simulate and debug {grokLogParsingTool} patterns for data transformation on ingestion.', - values: { - grokLogParsingTool: 'grok', - }, - }), - icon: 'grokApp', - path: '/app/kibana#/dev_tools/grokdebugger', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts b/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts new file mode 100644 index 0000000000000..97d2e53ce7836 --- /dev/null +++ b/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register({ + id: 'grokdebugger', + title: i18n.translate('xpack.grokDebugger.registryProviderTitle', { + defaultMessage: '{grokLogParsingTool} Debugger', + values: { + grokLogParsingTool: 'Grok', + }, + }), + description: i18n.translate('xpack.grokDebugger.registryProviderDescription', { + defaultMessage: + 'Simulate and debug {grokLogParsingTool} patterns for data transformation on ingestion.', + values: { + grokLogParsingTool: 'grok', + }, + }), + icon: 'grokApp', + path: '/app/kibana#/dev_tools/grokdebugger', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, +}); diff --git a/x-pack/legacy/plugins/index_management/common/constants/plugin.ts b/x-pack/legacy/plugins/index_management/common/constants/plugin.ts index 1f283464df9a0..2cd137f62d3db 100644 --- a/x-pack/legacy/plugins/index_management/common/constants/plugin.ts +++ b/x-pack/legacy/plugins/index_management/common/constants/plugin.ts @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC } from '../../../../common/constants'; +import { LicenseType } from '../../../../../plugins/licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; export const PLUGIN = { - ID: 'index_management', + id: 'index_management', + minimumLicenseType: basicLicense, getI18nName: (i18n: any): string => i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management', }), - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC, }; diff --git a/x-pack/legacy/plugins/spaces/common/model/types.ts b/x-pack/legacy/plugins/index_management/common/index.ts similarity index 78% rename from x-pack/legacy/plugins/spaces/common/model/types.ts rename to x-pack/legacy/plugins/index_management/common/index.ts index 58c36da33dbd7..0cc4ba79711ce 100644 --- a/x-pack/legacy/plugins/spaces/common/model/types.ts +++ b/x-pack/legacy/plugins/index_management/common/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export type GetSpacePurpose = 'any' | 'copySavedObjectsIntoSpace'; +export { PLUGIN, API_BASE_PATH } from './constants'; diff --git a/x-pack/legacy/plugins/index_management/index.ts b/x-pack/legacy/plugins/index_management/index.ts index f2a543337199f..c92b38c0d94be 100644 --- a/x-pack/legacy/plugins/index_management/index.ts +++ b/x-pack/legacy/plugins/index_management/index.ts @@ -5,19 +5,15 @@ */ import { resolve } from 'path'; -import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; -import { createRouter } from '../../server/lib/create_router'; -import { registerLicenseChecker } from '../../server/lib/register_license_checker'; -import { PLUGIN, API_BASE_PATH } from './common/constants'; -import { LegacySetup } from './server/plugin'; -import { plugin as initServerPlugin } from './server'; +import { PLUGIN } from './common/constants'; +import { plugin as initServerPlugin, Dependencies } from './server'; export type ServerFacade = Legacy.Server; export function indexManagement(kibana: any) { return new kibana.Plugin({ - id: PLUGIN.ID, + id: PLUGIN.id, configPrefix: 'xpack.index_management', publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], @@ -29,32 +25,15 @@ export function indexManagement(kibana: any) { init(server: ServerFacade) { const coreSetup = server.newPlatform.setup.core; - - const pluginsSetup = {}; - - const __LEGACY: LegacySetup = { - router: createRouter(server, PLUGIN.ID, `${API_BASE_PATH}/`), - plugins: { - license: { - registerLicenseChecker: registerLicenseChecker.bind( - null, - server, - PLUGIN.ID, - PLUGIN.getI18nName(i18n), - PLUGIN.MINIMUM_LICENSE_REQUIRED as 'basic' - ), - }, - elasticsearch: server.plugins.elasticsearch, - }, + const coreInitializerContext = server.newPlatform.coreContext; + const pluginsSetup: Dependencies = { + licensing: server.newPlatform.setup.plugins.licensing as any, }; - const serverPlugin = initServerPlugin(); - const indexMgmtSetup = serverPlugin.setup(coreSetup, pluginsSetup, __LEGACY); + const serverPlugin = initServerPlugin(coreInitializerContext as any); + const serverPublicApi = serverPlugin.setup(coreSetup, pluginsSetup); - server.expose( - 'addIndexManagementDataEnricher', - indexMgmtSetup.addIndexManagementDataEnricher - ); + server.expose('addIndexManagementDataEnricher', serverPublicApi.indexDataEnricher.add); }, }); } diff --git a/x-pack/legacy/plugins/index_management/server/index.ts b/x-pack/legacy/plugins/index_management/server/index.ts index c405f7816337d..866b374740d3b 100644 --- a/x-pack/legacy/plugins/index_management/server/index.ts +++ b/x-pack/legacy/plugins/index_management/server/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IndexMgmtPlugin } from './plugin'; -export function plugin() { - return new IndexMgmtPlugin(); -} +import { PluginInitializerContext } from 'src/core/server'; +import { IndexMgmtServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new IndexMgmtServerPlugin(ctx); + +export { Dependencies } from './types'; diff --git a/x-pack/legacy/plugins/index_management/server/index_management_data.ts b/x-pack/legacy/plugins/index_management/server/index_management_data.ts deleted file mode 100644 index e730761979e1c..0000000000000 --- a/x-pack/legacy/plugins/index_management/server/index_management_data.ts +++ /dev/null @@ -1,15 +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. - */ - -const indexManagementDataEnrichers: any[] = []; - -export const addIndexManagementDataEnricher = (enricher: any) => { - indexManagementDataEnrichers.push(enricher); -}; - -export const getIndexManagementDataEnrichers = () => { - return indexManagementDataEnrichers; -}; diff --git a/x-pack/legacy/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/legacy/plugins/index_management/server/lib/fetch_indices.ts index 19a5cd81c919b..d9f01ee060145 100644 --- a/x-pack/legacy/plugins/index_management/server/lib/fetch_indices.ts +++ b/x-pack/legacy/plugins/index_management/server/lib/fetch_indices.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { IndexDataEnricher } from '../services'; +import { Index, CallAsCurrentUser } from '../types'; import { fetchAliases } from './fetch_aliases'; -import { getIndexManagementDataEnrichers } from '../index_management_data'; + interface Hit { health: string; status: string; @@ -27,22 +29,7 @@ interface Params { index?: string[]; } -const enrichResponse = async (response: any, callWithRequest: any) => { - let enrichedResponse = response; - const dataEnrichers = getIndexManagementDataEnrichers(); - for (let i = 0; i < dataEnrichers.length; i++) { - const dataEnricher = dataEnrichers[i]; - try { - const dataEnricherResponse = await dataEnricher(enrichedResponse, callWithRequest); - enrichedResponse = dataEnricherResponse; - } catch (e) { - // silently swallow enricher response errors - } - } - return enrichedResponse; -}; - -function formatHits(hits: Hit[], aliases: Aliases) { +function formatHits(hits: Hit[], aliases: Aliases): Index[] { return hits.map((hit: Hit) => { return { health: hit.health, @@ -59,7 +46,7 @@ function formatHits(hits: Hit[], aliases: Aliases) { }); } -async function fetchIndicesCall(callWithRequest: any, indexNames?: string[]) { +async function fetchIndicesCall(callAsCurrentUser: CallAsCurrentUser, indexNames?: string[]) { const params: Params = { format: 'json', h: 'health,status,index,uuid,pri,rep,docs.count,sth,store.size', @@ -69,13 +56,17 @@ async function fetchIndicesCall(callWithRequest: any, indexNames?: string[]) { params.index = indexNames; } - return await callWithRequest('cat.indices', params); + return await callAsCurrentUser('cat.indices', params); } -export const fetchIndices = async (callWithRequest: any, indexNames?: string[]) => { - const aliases = await fetchAliases(callWithRequest); - const hits = await fetchIndicesCall(callWithRequest, indexNames); - let response = formatHits(hits, aliases); - response = await enrichResponse(response, callWithRequest); - return response; +export const fetchIndices = async ( + callAsCurrentUser: CallAsCurrentUser, + indexDataEnricher: IndexDataEnricher, + indexNames?: string[] +) => { + const aliases = await fetchAliases(callAsCurrentUser); + const hits = await fetchIndicesCall(callAsCurrentUser, indexNames); + const indices = formatHits(hits, aliases); + + return await indexDataEnricher.enrichIndices(indices, callAsCurrentUser); }; diff --git a/x-pack/legacy/plugins/index_management/server/lib/get_managed_templates.ts b/x-pack/legacy/plugins/index_management/server/lib/get_managed_templates.ts index ebffe73eb23a4..2fdb21ea4b0d6 100644 --- a/x-pack/legacy/plugins/index_management/server/lib/get_managed_templates.ts +++ b/x-pack/legacy/plugins/index_management/server/lib/get_managed_templates.ts @@ -7,10 +7,10 @@ // Cloud has its own system for managing templates and we want to make // this clear in the UI when a template is used in a Cloud deployment. export const getManagedTemplatePrefix = async ( - callWithInternalUser: any + callAsCurrentUser: any ): Promise => { try { - const { persistent, transient, defaults } = await callWithInternalUser('cluster.getSettings', { + const { persistent, transient, defaults } = await callAsCurrentUser('cluster.getSettings', { filterPath: '*.*managed_index_templates', flatSettings: true, includeDefaults: true, diff --git a/x-pack/legacy/plugins/spaces/common/model/space.ts b/x-pack/legacy/plugins/index_management/server/lib/is_es_error.ts similarity index 55% rename from x-pack/legacy/plugins/spaces/common/model/space.ts rename to x-pack/legacy/plugins/index_management/server/lib/is_es_error.ts index c44ce41ec51c0..4137293cf39c0 100644 --- a/x-pack/legacy/plugins/spaces/common/model/space.ts +++ b/x-pack/legacy/plugins/index_management/server/lib/is_es_error.ts @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface Space { - id: string; - name: string; - description?: string; - color?: string; - initials?: string; - disabledFeatures: string[]; - _reserved?: boolean; - imageUrl?: string; +import * as legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; } diff --git a/x-pack/legacy/plugins/index_management/server/plugin.ts b/x-pack/legacy/plugins/index_management/server/plugin.ts index cbe19adcd58be..95d27e1cf16ba 100644 --- a/x-pack/legacy/plugins/index_management/server/plugin.ts +++ b/x-pack/legacy/plugins/index_management/server/plugin.ts @@ -3,48 +3,67 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/server'; -import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; -import { Router } from '../../../server/lib/create_router'; -import { addIndexManagementDataEnricher } from './index_management_data'; -import { registerIndicesRoutes } from './routes/api/indices'; -import { registerTemplateRoutes } from './routes/api/templates'; -import { registerMappingRoute } from './routes/api/mapping'; -import { registerSettingsRoutes } from './routes/api/settings'; -import { registerStatsRoute } from './routes/api/stats'; - -export interface LegacySetup { - router: Router; - plugins: { - elasticsearch: ElasticsearchPlugin; - license: { - registerLicenseChecker: () => void; - }; - }; -} +import { i18n } from '@kbn/i18n'; +import { CoreSetup, Plugin, Logger, PluginInitializerContext } from 'src/core/server'; + +import { PLUGIN } from '../common'; +import { Dependencies } from './types'; +import { ApiRoutes } from './routes'; +import { License, IndexDataEnricher } from './services'; +import { isEsError } from './lib/is_es_error'; export interface IndexMgmtSetup { - addIndexManagementDataEnricher: (enricher: any) => void; + indexDataEnricher: { + add: IndexDataEnricher['add']; + }; } -export class IndexMgmtPlugin { - public setup(core: CoreSetup, plugins: {}, __LEGACY: LegacySetup): IndexMgmtSetup { - const serverFacade = { - plugins: { - elasticsearch: __LEGACY.plugins.elasticsearch, - }, - }; +export class IndexMgmtServerPlugin implements Plugin { + private readonly apiRoutes: ApiRoutes; + private readonly license: License; + private readonly logger: Logger; + private readonly indexDataEnricher: IndexDataEnricher; - __LEGACY.plugins.license.registerLicenseChecker(); + constructor({ logger }: PluginInitializerContext) { + this.logger = logger.get(); + this.apiRoutes = new ApiRoutes(); + this.license = new License(); + this.indexDataEnricher = new IndexDataEnricher(); + } + + setup({ http }: CoreSetup, { licensing }: Dependencies): IndexMgmtSetup { + const router = http.createRouter(); - registerIndicesRoutes(__LEGACY.router); - registerTemplateRoutes(__LEGACY.router, serverFacade); - registerSettingsRoutes(__LEGACY.router); - registerStatsRoute(__LEGACY.router); - registerMappingRoute(__LEGACY.router); + this.license.setup( + { + pluginId: PLUGIN.id, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.idxMgmt.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }, + { + licensing, + logger: this.logger, + } + ); + + this.apiRoutes.setup({ + router, + license: this.license, + indexDataEnricher: this.indexDataEnricher, + lib: { + isEsError, + }, + }); return { - addIndexManagementDataEnricher, + indexDataEnricher: { + add: this.indexDataEnricher.add.bind(this.indexDataEnricher), + }, }; } + + start() {} + stop() {} } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/index.ts b/x-pack/legacy/plugins/index_management/server/routes/api/index.ts new file mode 100644 index 0000000000000..4ed008480c149 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/routes/api/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { API_BASE_PATH } from '../../../common'; + +export const addBasePath = (uri: string): string => API_BASE_PATH + uri; diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index 8bd370a3eb3b8..ec42b2aee45a9 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -3,26 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; +export function registerClearCacheRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/clear_cache'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const payload = req.body as typeof bodySchema.type; + const { indices = [] } = payload; - await callWithRequest('indices.clearCache', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerClearCacheRoute(router: Router) { - router.post('indices/clear_cache', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.clearCache', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_close_route.ts index 6e304f1762acc..bd243ab3e5de5 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -3,26 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; +export function registerCloseRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/close'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const payload = req.body as typeof bodySchema.type; + const { indices = [] } = payload; - await callWithRequest('indices.close', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerCloseRoute(router: Router) { - router.post('indices/close', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.close', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_delete_route.ts index 0d2268eca179d..ffe30b315363a 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -4,25 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +export function registerDeleteRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/delete'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + const { indices = [] } = body; - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; - await callWithRequest('indices.delete', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerDeleteRoute(router: Router) { - router.post('indices/delete', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.delete', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_flush_route.ts index 0623d80305719..fee3a0f5278da 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -4,26 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; +export function registerFlushRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/flush'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + const { indices = [] } = body; - await callWithRequest('indices.flush', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerFlushRoute(router: Router) { - router.post('indices/flush', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.flush', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index c0a0ae48c34b8..c39547a3cbd40 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -4,34 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ForceMergeReqPayload { - maxNumSegments: number; - indices: string[]; -} - -interface Params { - expandWildcards: string; - index: ForceMergeReqPayload['indices']; - max_num_segments?: ForceMergeReqPayload['maxNumSegments']; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const { maxNumSegments, indices = [] } = request.payload as ForceMergeReqPayload; - const params: Params = { - expandWildcards: 'none', - index: indices, - }; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), + maxNumSegments: schema.maybe(schema.number()), +}); - if (maxNumSegments) { - params.max_num_segments = maxNumSegments; - } +export function registerForcemergeRoute({ router, license, lib }: RouteDependencies) { + router.post( + { + path: addBasePath('/indices/forcemerge'), + validate: { + body: bodySchema, + }, + }, + license.guardApiRoute(async (ctx, req, res) => { + const { maxNumSegments, indices = [] } = req.body as typeof bodySchema.type; + const params = { + expandWildcards: 'none', + index: indices, + }; - await callWithRequest('indices.forcemerge', params); - return h.response(); -}; + if (maxNumSegments) { + (params as any).max_num_segments = maxNumSegments; + } -export function registerForcemergeRoute(router: Router) { - router.post('indices/forcemerge', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.forcemerge', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_freeze_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_freeze_route.ts index 658a904f08fe7..68bb4b13ef475 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_freeze_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_freeze_route.ts @@ -4,25 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); - const params = { - path: `/${encodeURIComponent(indices.join(','))}/_freeze`, - method: 'POST', - }; +export function registerFreezeRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/freeze'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + const { indices = [] } = body; - await callWithRequest('transport.request', params); - return h.response(); -}; + const params = { + path: `/${encodeURIComponent(indices.join(','))}/_freeze`, + method: 'POST', + }; -export function registerFreezeRoute(router: Router) { - router.post('indices/freeze', handler); + try { + await await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + 'transport.request', + params + ); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_indices_routes.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_indices_routes.ts index 977ef689f44b9..e1165b5d689a0 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_indices_routes.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_indices_routes.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router } from '../../../../../../server/lib/create_router'; +import { RouteDependencies } from '../../../types'; import { registerClearCacheRoute } from './register_clear_cache_route'; import { registerCloseRoute } from './register_close_route'; @@ -17,16 +17,16 @@ import { registerDeleteRoute } from './register_delete_route'; import { registerFreezeRoute } from './register_freeze_route'; import { registerUnfreezeRoute } from './register_unfreeze_route'; -export function registerIndicesRoutes(router: Router) { - registerClearCacheRoute(router); - registerCloseRoute(router); - registerFlushRoute(router); - registerForcemergeRoute(router); - registerListRoute(router); - registerOpenRoute(router); - registerRefreshRoute(router); - registerReloadRoute(router); - registerDeleteRoute(router); - registerFreezeRoute(router); - registerUnfreezeRoute(router); +export function registerIndicesRoutes(dependencies: RouteDependencies) { + registerClearCacheRoute(dependencies); + registerCloseRoute(dependencies); + registerFlushRoute(dependencies); + registerForcemergeRoute(dependencies); + registerListRoute(dependencies); + registerOpenRoute(dependencies); + registerRefreshRoute(dependencies); + registerReloadRoute(dependencies); + registerDeleteRoute(dependencies); + registerFreezeRoute(dependencies); + registerUnfreezeRoute(dependencies); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_list_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_list_route.ts index d8b8018a975c4..1f5d8ddf529eb 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_list_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_list_route.ts @@ -3,14 +3,31 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; import { fetchIndices } from '../../../lib/fetch_indices'; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest) => { - return fetchIndices(callWithRequest); -}; - -export function registerListRoute(router: Router) { - router.get('indices', handler); +export function registerListRoute({ router, license, indexDataEnricher, lib }: RouteDependencies) { + router.get( + { path: addBasePath('/indices'), validate: false }, + license.guardApiRoute(async (ctx, req, res) => { + try { + const indices = await fetchIndices( + ctx.core.elasticsearch.dataClient.callAsCurrentUser, + indexDataEnricher + ); + return res.ok({ body: indices }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_open_route.ts index 50c2540ec0045..28dbae0d8864b 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -3,25 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +export function registerOpenRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/open'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + const { indices = [] } = body; - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; - await callWithRequest('indices.open', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerOpenRoute(router: Router) { - router.post('indices/open', handler); + try { + await await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.open', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index 093075652821b..34fee477662e8 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -4,25 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const payload = request.payload as ReqPayload; - const { indices = [] } = payload; +export function registerRefreshRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/refresh'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const body = req.body as typeof bodySchema.type; + const { indices = [] } = body; - const params = { - expandWildcards: 'none', - format: 'json', - index: indices, - }; - await callWithRequest('indices.refresh', params); - return h.response(); -}; + const params = { + expandWildcards: 'none', + format: 'json', + index: indices, + }; -export function registerRefreshRoute(router: Router) { - router.post('indices/refresh', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.refresh', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_reload_route.ts index 7371cc1c2d9f1..22a9d79439ab0 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -3,19 +3,46 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; +import { RouteDependencies } from '../../../types'; import { fetchIndices } from '../../../lib/fetch_indices'; +import { addBasePath } from '../index'; -interface ReqPayload { - indexNames: string[]; -} +const bodySchema = schema.maybe( + schema.object({ + indexNames: schema.maybe(schema.arrayOf(schema.string())), + }) +); -const handler: RouterRouteHandler = async (request, callWithRequest) => { - const { indexNames = [] } = request.payload as ReqPayload; - return fetchIndices(callWithRequest, indexNames); -}; +export function registerReloadRoute({ + router, + license, + indexDataEnricher, + lib, +}: RouteDependencies) { + router.post( + { path: addBasePath('/indices/reload'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { indexNames = [] } = (req.body as typeof bodySchema.type) ?? {}; -export function registerReloadRoute(router: Router) { - router.post('indices/reload', handler); + try { + const indices = await fetchIndices( + ctx.core.elasticsearch.dataClient.callAsCurrentUser, + indexDataEnricher, + indexNames + ); + return res.ok({ body: indices }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts index 0db882c5171e8..67c4a3516d1e6 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts @@ -4,23 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -interface ReqPayload { - indices: string[]; -} +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -const handler: RouterRouteHandler = async (request, callWithRequest, h) => { - const { indices = [] } = request.payload as ReqPayload; - const params = { - path: `/${encodeURIComponent(indices.join(','))}/_unfreeze`, - method: 'POST', - }; +const bodySchema = schema.object({ + indices: schema.arrayOf(schema.string()), +}); - await callWithRequest('transport.request', params); - return h.response(); -}; +export function registerUnfreezeRoute({ router, license, lib }: RouteDependencies) { + router.post( + { path: addBasePath('/indices/unfreeze'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { indices = [] } = req.body as typeof bodySchema.type; + const params = { + path: `/${encodeURIComponent(indices.join(','))}/_unfreeze`, + method: 'POST', + }; -export function registerUnfreezeRoute(router: Router) { - router.post('indices/unfreeze', handler); + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('transport.request', params); + return res.ok(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts index 86600aab76580..20d7e6b4d7232 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts @@ -3,7 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const paramsSchema = schema.object({ + indexName: schema.string(), +}); function formatHit(hit: { [key: string]: { mappings: any } }, indexName: string) { const mapping = hit[indexName].mappings; @@ -12,18 +19,33 @@ function formatHit(hit: { [key: string]: { mappings: any } }, indexName: string) }; } -const handler: RouterRouteHandler = async (request, callWithRequest) => { - const { indexName } = request.params; - const params = { - expand_wildcards: 'none', - index: indexName, - }; - - const hit = await callWithRequest('indices.getMapping', params); - const response = formatHit(hit, indexName); - return response; -}; +export function registerMappingRoute({ router, license, lib }: RouteDependencies) { + router.get( + { path: addBasePath('/mapping/{indexName}'), validate: { params: paramsSchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { indexName } = req.params as typeof paramsSchema.type; + const params = { + expand_wildcards: 'none', + index: indexName, + }; -export function registerMappingRoute(router: Router) { - router.get('mapping/{indexName}', handler); + try { + const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.getMapping', + params + ); + const response = formatHit(hit, indexName); + return res.ok({ body: response }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_load_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_load_route.ts index 70b96c3912e72..c31813b4a9f49 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_load_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_load_route.ts @@ -3,7 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const paramsSchema = schema.object({ + indexName: schema.string(), +}); // response comes back as { [indexName]: { ... }} // so plucking out the embedded object @@ -12,19 +19,35 @@ function formatHit(hit: { [key: string]: {} }) { return hit[key]; } -const handler: RouterRouteHandler = async (request, callWithRequest) => { - const { indexName } = request.params; - const params = { - expandWildcards: 'none', - flatSettings: false, - local: false, - includeDefaults: true, - index: indexName, - }; +export function registerLoadRoute({ router, license, lib }: RouteDependencies) { + router.get( + { path: addBasePath('/settings/{indexName}'), validate: { params: paramsSchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { indexName } = req.params as typeof paramsSchema.type; + const params = { + expandWildcards: 'none', + flatSettings: false, + local: false, + includeDefaults: true, + index: indexName, + }; - const hit = await callWithRequest('indices.getSettings', params); - return formatHit(hit); -}; -export function registerLoadRoute(router: Router) { - router.get('settings/{indexName}', handler); + try { + const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.getSettings', + params + ); + return res.ok({ body: formatHit(hit) }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_settings_routes.ts b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_settings_routes.ts index 2fe1786f266bd..501566f8b62d8 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_settings_routes.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_settings_routes.ts @@ -3,12 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router } from '../../../../../../server/lib/create_router'; +import { RouteDependencies } from '../../../types'; import { registerLoadRoute } from './register_load_route'; import { registerUpdateRoute } from './register_update_route'; -export function registerSettingsRoutes(router: Router) { - registerLoadRoute(router); - registerUpdateRoute(router); +export function registerSettingsRoutes(dependencies: RouteDependencies) { + registerLoadRoute(dependencies); + registerUpdateRoute(dependencies); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_update_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_update_route.ts index 4d28b5b4ac3bf..9ce5ae7f99393 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_update_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/settings/register_update_route.ts @@ -3,20 +3,49 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; -const handler: RouterRouteHandler = async (request, callWithRequest) => { - const { indexName } = request.params; - const params = { - ignoreUnavailable: true, - allowNoIndices: false, - expandWildcards: 'none', - index: indexName, - body: request.payload, - }; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; - return await callWithRequest('indices.putSettings', params); -}; -export function registerUpdateRoute(router: Router) { - router.put('settings/{indexName}', handler); +const bodySchema = schema.any(); + +const paramsSchema = schema.object({ + indexName: schema.string(), +}); + +export function registerUpdateRoute({ router, license, lib }: RouteDependencies) { + router.put( + { + path: addBasePath('/settings/{indexName}'), + validate: { body: bodySchema, params: paramsSchema }, + }, + license.guardApiRoute(async (ctx, req, res) => { + const { indexName } = req.params as typeof paramsSchema.type; + const params = { + ignoreUnavailable: true, + allowNoIndices: false, + expandWildcards: 'none', + index: indexName, + body: req.body, + }; + + try { + const response = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.putSettings', + params + ); + return res.ok({ body: response }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/stats/register_stats_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/stats/register_stats_route.ts index 33d0df53e079b..f408fd6584bd5 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/stats/register_stats_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/stats/register_stats_route.ts @@ -3,7 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +const paramsSchema = schema.object({ + indexName: schema.string(), +}); function formatHit(hit: { _shards: any; indices: { [key: string]: any } }, indexName: string) { const { _shards, indices } = hit; @@ -14,17 +21,32 @@ function formatHit(hit: { _shards: any; indices: { [key: string]: any } }, index }; } -const handler: RouterRouteHandler = async (request, callWithRequest) => { - const { indexName } = request.params; - const params = { - expand_wildcards: 'none', - index: indexName, - }; - const hit = await callWithRequest('indices.stats', params); - const response = formatHit(hit, indexName); +export function registerStatsRoute({ router, license, lib }: RouteDependencies) { + router.get( + { path: addBasePath('/stats/{indexName}'), validate: { params: paramsSchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { indexName } = req.params as typeof paramsSchema.type; + const params = { + expand_wildcards: 'none', + index: indexName, + }; - return response; -}; -export function registerStatsRoute(router: Router) { - router.get('stats/{indexName}', handler); + try { + const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.stats', + params + ); + return res.ok({ body: formatHit(hit, indexName) }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_create_route.ts index e134a97dd029e..817893976767f 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_create_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_create_route.ts @@ -5,60 +5,74 @@ */ import { i18n } from '@kbn/i18n'; -import { - Router, - RouterRouteHandler, - wrapCustomError, -} from '../../../../../../server/lib/create_router'; + import { Template, TemplateEs } from '../../../../common/types'; import { serializeTemplate } from '../../../../common/lib'; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; +import { templateSchema } from './validate_schemas'; -const handler: RouterRouteHandler = async (req, callWithRequest) => { - const template = req.payload as Template; - const serializedTemplate = serializeTemplate(template) as TemplateEs; +const bodySchema = templateSchema; - const { name, order, index_patterns, version, settings, mappings, aliases } = serializedTemplate; +export function registerCreateRoute({ router, license, lib }: RouteDependencies) { + router.put( + { path: addBasePath('/templates'), validate: { body: bodySchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const template = req.body as Template; + const serializedTemplate = serializeTemplate(template) as TemplateEs; - const conflictError = wrapCustomError( - new Error( - i18n.translate('xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage', { - defaultMessage: "There is already a template with name '{name}'.", - values: { - name, - }, - }) - ), - 409 - ); + const { + name, + order, + index_patterns, + version, + settings, + mappings, + aliases, + } = serializedTemplate; - // Check that template with the same name doesn't already exist - try { - const templateExists = await callWithRequest('indices.existsTemplate', { name }); + // Check that template with the same name doesn't already exist + const templateExists = await callAsCurrentUser('indices.existsTemplate', { name }); - if (templateExists) { - throw conflictError; - } - } catch (e) { - // Rethrow conflict error but silently swallow all others - if (e === conflictError) { - throw e; - } - } + if (templateExists) { + return res.conflict({ + body: new Error( + i18n.translate('xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage', { + defaultMessage: "There is already a template with name '{name}'.", + values: { + name, + }, + }) + ), + }); + } - // Otherwise create new index template - return await callWithRequest('indices.putTemplate', { - name, - order, - body: { - index_patterns, - version, - settings, - mappings, - aliases, - }, - }); -}; + try { + // Otherwise create new index template + const response = await callAsCurrentUser('indices.putTemplate', { + name, + order, + body: { + index_patterns, + version, + settings, + mappings, + aliases, + }, + }); -export function registerCreateRoute(router: Router) { - router.put('templates', handler); + return res.ok({ body: response }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_delete_route.ts index b48354127b9f9..c9f1995204d8c 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_delete_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_delete_route.ts @@ -4,38 +4,46 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Router, - RouterRouteHandler, - wrapEsError, -} from '../../../../../../server/lib/create_router'; +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; +import { wrapEsError } from '../../helpers'; + import { Template } from '../../../../common/types'; -const handler: RouterRouteHandler = async (req, callWithRequest) => { - const { names } = req.params; - const templateNames = names.split(','); - const response: { templatesDeleted: Array; errors: any[] } = { - templatesDeleted: [], - errors: [], - }; +const paramsSchema = schema.object({ + names: schema.string(), +}); - await Promise.all( - templateNames.map(async name => { - try { - await callWithRequest('indices.deleteTemplate', { name }); - return response.templatesDeleted.push(name); - } catch (e) { - return response.errors.push({ - name, - error: wrapEsError(e), - }); - } - }) - ); +export function registerDeleteRoute({ router, license }: RouteDependencies) { + router.delete( + { path: addBasePath('/templates/{names}'), validate: { params: paramsSchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { names } = req.params as typeof paramsSchema.type; + const templateNames = names.split(','); + const response: { templatesDeleted: Array; errors: any[] } = { + templatesDeleted: [], + errors: [], + }; - return response; -}; + await Promise.all( + templateNames.map(async name => { + try { + await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.deleteTemplate', { + name, + }); + return response.templatesDeleted.push(name); + } catch (e) { + return response.errors.push({ + name, + error: wrapEsError(e), + }); + } + }) + ); -export function registerDeleteRoute(router: Router) { - router.delete('templates/{names}', handler); + return res.ok({ body: response }); + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_get_routes.ts index b450f75d1cc53..d6776d774d3bf 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_get_routes.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_get_routes.ts @@ -3,37 +3,62 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { schema } from '@kbn/config-schema'; import { deserializeTemplate, deserializeTemplateList } from '../../../../common/lib'; -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; import { getManagedTemplatePrefix } from '../../../lib/get_managed_templates'; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; -let callWithInternalUser: any; +export function registerGetAllRoute({ router, license }: RouteDependencies) { + router.get( + { path: addBasePath('/templates'), validate: false }, + license.guardApiRoute(async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser); -const allHandler: RouterRouteHandler = async (_req, callWithRequest) => { - const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser); + const indexTemplatesByName = await callAsCurrentUser('indices.getTemplate'); - const indexTemplatesByName = await callWithRequest('indices.getTemplate'); + return res.ok({ body: deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix) }); + }) + ); +} - return deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix); -}; +const paramsSchema = schema.object({ + name: schema.string(), +}); -const oneHandler: RouterRouteHandler = async (req, callWithRequest) => { - const { name } = req.params; - const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser); - const indexTemplateByName = await callWithRequest('indices.getTemplate', { name }); +export function registerGetOneRoute({ router, license, lib }: RouteDependencies) { + router.get( + { path: addBasePath('/templates/{name}'), validate: { params: paramsSchema } }, + license.guardApiRoute(async (ctx, req, res) => { + const { name } = req.params as typeof paramsSchema.type; + const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; - if (indexTemplateByName[name]) { - return deserializeTemplate({ ...indexTemplateByName[name], name }, managedTemplatePrefix); - } -}; + try { + const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser); + const indexTemplateByName = await callAsCurrentUser('indices.getTemplate', { name }); -export function registerGetAllRoute(router: Router, server: any) { - callWithInternalUser = server.plugins.elasticsearch.getCluster('data').callWithInternalUser; - router.get('templates', allHandler); -} + if (indexTemplateByName[name]) { + return res.ok({ + body: deserializeTemplate( + { ...indexTemplateByName[name], name }, + managedTemplatePrefix + ), + }); + } -export function registerGetOneRoute(router: Router, server: any) { - callWithInternalUser = server.plugins.elasticsearch.getCluster('data').callWithInternalUser; - router.get('templates/{name}', oneHandler); + return res.notFound(); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_template_routes.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_template_routes.ts index b1dcad3f4c362..2b657346a2f82 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_template_routes.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_template_routes.ts @@ -4,16 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Router } from '../../../../../../server/lib/create_router'; +import { RouteDependencies } from '../../../types'; + import { registerGetAllRoute, registerGetOneRoute } from './register_get_routes'; import { registerDeleteRoute } from './register_delete_route'; import { registerCreateRoute } from './register_create_route'; import { registerUpdateRoute } from './register_update_route'; -export function registerTemplateRoutes(router: Router, server: any) { - registerGetAllRoute(router, server); - registerGetOneRoute(router, server); - registerDeleteRoute(router); - registerCreateRoute(router); - registerUpdateRoute(router); +export function registerTemplateRoutes(dependencies: RouteDependencies) { + registerGetAllRoute(dependencies); + registerGetOneRoute(dependencies); + registerDeleteRoute(dependencies); + registerCreateRoute(dependencies); + registerUpdateRoute(dependencies); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_update_route.ts index 15590e2acbe71..e7f541fa67f8a 100644 --- a/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_update_route.ts +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/register_update_route.ts @@ -3,35 +3,65 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { schema } from '@kbn/config-schema'; -import { Router, RouterRouteHandler } from '../../../../../../server/lib/create_router'; import { Template, TemplateEs } from '../../../../common/types'; import { serializeTemplate } from '../../../../common/lib'; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; +import { templateSchema } from './validate_schemas'; -const handler: RouterRouteHandler = async (req, callWithRequest) => { - const { name } = req.params; - const template = req.payload as Template; - const serializedTemplate = serializeTemplate(template) as TemplateEs; - - const { order, index_patterns, version, settings, mappings, aliases } = serializedTemplate; - - // Verify the template exists (ES will throw 404 if not) - await callWithRequest('indices.existsTemplate', { name }); - - // Next, update index template - return await callWithRequest('indices.putTemplate', { - name, - order, - body: { - index_patterns, - version, - settings, - mappings, - aliases, +const bodySchema = templateSchema; +const paramsSchema = schema.object({ + name: schema.string(), +}); + +export function registerUpdateRoute({ router, license, lib }: RouteDependencies) { + router.put( + { + path: addBasePath('/templates/{name}'), + validate: { body: bodySchema, params: paramsSchema }, }, - }); -}; + license.guardApiRoute(async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { name } = req.params as typeof paramsSchema.type; + const template = req.body as Template; + const serializedTemplate = serializeTemplate(template) as TemplateEs; + + const { order, index_patterns, version, settings, mappings, aliases } = serializedTemplate; + + // Verify the template exists (ES will throw 404 if not) + const doesExist = await callAsCurrentUser('indices.existsTemplate', { name }); + + if (!doesExist) { + return res.notFound(); + } + + try { + // Next, update index template + const response = await callAsCurrentUser('indices.putTemplate', { + name, + order, + body: { + index_patterns, + version, + settings, + mappings, + aliases, + }, + }); -export function registerUpdateRoute(router: Router) { - router.put('templates/{name}', handler); + return res.ok({ body: response }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); + } + // Case: default + return res.internalError({ body: e }); + } + }) + ); } diff --git a/x-pack/legacy/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/legacy/plugins/index_management/server/routes/api/templates/validate_schemas.ts new file mode 100644 index 0000000000000..fb5d41870eece --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/routes/api/templates/validate_schemas.ts @@ -0,0 +1,24 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const templateSchema = schema.object({ + name: schema.string(), + indexPatterns: schema.arrayOf(schema.string()), + version: schema.maybe(schema.number()), + order: schema.maybe(schema.number()), + settings: schema.maybe(schema.object({}, { allowUnknowns: true })), + aliases: schema.maybe(schema.object({}, { allowUnknowns: true })), + mappings: schema.maybe(schema.object({}, { allowUnknowns: true })), + ilmPolicy: schema.maybe( + schema.object({ + name: schema.maybe(schema.string()), + rollover_alias: schema.maybe(schema.string()), + }) + ), + isManaged: schema.maybe(schema.boolean()), +}); diff --git a/x-pack/legacy/plugins/index_management/server/routes/helpers.ts b/x-pack/legacy/plugins/index_management/server/routes/helpers.ts new file mode 100644 index 0000000000000..6cd4b0dc80e22 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/routes/helpers.ts @@ -0,0 +1,58 @@ +/* + * 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. + */ + +const extractCausedByChain = (causedBy: any = {}, accumulator: any[] = []): any => { + const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/camelcase + + if (reason) { + accumulator.push(reason); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + if (caused_by) { + return extractCausedByChain(caused_by, accumulator); + } + + return accumulator; +}; + +/** + * Wraps an error thrown by the ES JS client into a Boom error response and returns it + * + * @param err Object Error thrown by ES JS client + * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages + * @return Object Boom error response + */ +export const wrapEsError = (err: any, statusCodeToMessageMap: any = {}) => { + const { statusCode, response } = err; + + const { + error: { + root_cause = [], // eslint-disable-line @typescript-eslint/camelcase + caused_by = {}, // eslint-disable-line @typescript-eslint/camelcase + } = {}, + } = JSON.parse(response); + + // If no custom message if specified for the error's status code, just + // wrap the error as a Boom error response, include the additional information from ES, and return it + if (!statusCodeToMessageMap[statusCode]) { + // const boomError = Boom.boomify(err, { statusCode }); + const error: any = { statusCode }; + + // The caused_by chain has the most information so use that if it's available. If not then + // settle for the root_cause. + const causedByChain = extractCausedByChain(caused_by); + const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; + + error.cause = causedByChain.length ? causedByChain : defaultCause; + return error; + } + + // Otherwise, use the custom message to create a Boom error response and + // return it + const message = statusCodeToMessageMap[statusCode]; + return { message, statusCode }; +}; diff --git a/x-pack/legacy/plugins/index_management/server/routes/index.ts b/x-pack/legacy/plugins/index_management/server/routes/index.ts new file mode 100644 index 0000000000000..870cfa36ecc6a --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/routes/index.ts @@ -0,0 +1,26 @@ +/* + * 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 { RouteDependencies } from '../types'; + +import { registerIndicesRoutes } from './api/indices'; +import { registerTemplateRoutes } from './api/templates'; +import { registerMappingRoute } from './api/mapping'; +import { registerSettingsRoutes } from './api/settings'; +import { registerStatsRoute } from './api/stats'; + +export class ApiRoutes { + setup(dependencies: RouteDependencies) { + registerIndicesRoutes(dependencies); + registerTemplateRoutes(dependencies); + registerSettingsRoutes(dependencies); + registerStatsRoute(dependencies); + registerMappingRoute(dependencies); + } + + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/index_management/server/services/index.ts b/x-pack/legacy/plugins/index_management/server/services/index.ts new file mode 100644 index 0000000000000..f1a2c2c009939 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/services/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { License } from './license'; + +export { IndexDataEnricher, Enricher } from './index_data_enricher'; diff --git a/x-pack/legacy/plugins/index_management/server/services/index_data_enricher.ts b/x-pack/legacy/plugins/index_management/server/services/index_data_enricher.ts new file mode 100644 index 0000000000000..7a62ce9f7a3c3 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/services/index_data_enricher.ts @@ -0,0 +1,40 @@ +/* + * 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 { Index, CallAsCurrentUser } from '../types'; + +export type Enricher = (indices: Index[], callAsCurrentUser: CallAsCurrentUser) => Promise; + +export class IndexDataEnricher { + private readonly _enrichers: Enricher[] = []; + + public add(enricher: Enricher) { + this._enrichers.push(enricher); + } + + public enrichIndices = async ( + indices: Index[], + callAsCurrentUser: CallAsCurrentUser + ): Promise => { + let enrichedIndices = indices; + + for (let i = 0; i < this.enrichers.length; i++) { + const dataEnricher = this.enrichers[i]; + try { + const dataEnricherResponse = await dataEnricher(enrichedIndices, callAsCurrentUser); + enrichedIndices = dataEnricherResponse; + } catch (e) { + // silently swallow enricher response errors + } + } + + return enrichedIndices; + }; + + public get enrichers() { + return this._enrichers; + } +} diff --git a/x-pack/legacy/plugins/index_management/server/services/license.ts b/x-pack/legacy/plugins/index_management/server/services/license.ts new file mode 100644 index 0000000000000..fc284a0e3eb65 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/services/license.ts @@ -0,0 +1,83 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'kibana/server'; + +import { LicensingPluginSetup } from '../../../../../plugins/licensing/server'; +import { LicenseType } from '../../../../../plugins/licensing/common/types'; +import { LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } +} diff --git a/x-pack/legacy/plugins/index_management/server/types.ts b/x-pack/legacy/plugins/index_management/server/types.ts new file mode 100644 index 0000000000000..fbc39b88a462e --- /dev/null +++ b/x-pack/legacy/plugins/index_management/server/types.ts @@ -0,0 +1,38 @@ +/* + * 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 { ScopedClusterClient, IRouter } from 'src/core/server'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; +import { License, IndexDataEnricher } from './services'; +import { isEsError } from './lib/is_es_error'; + +export interface Dependencies { + licensing: LicensingPluginSetup; +} + +export interface RouteDependencies { + router: IRouter; + license: License; + indexDataEnricher: IndexDataEnricher; + lib: { + isEsError: typeof isEsError; + }; +} + +export interface Index { + health: string; + status: string; + name: string; + uuid: string; + primary: string; + replica: string; + documents: any; + size: any; + isFrozen: boolean; + aliases: string | string[]; + [key: string]: any; +} + +export type CallAsCurrentUser = ScopedClusterClient['callAsCurrentUser']; diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index d9abadcb5125c..4ab2cde082498 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -42,7 +42,7 @@ export function infra(kibana: any) { url: `/app/${APP_ID}#/infrastructure`, }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), - home: ['plugins/infra/register_feature'], + home: ['plugins/infra/legacy_register_feature'], links: [ { description: i18n.translate('xpack.infra.linkInfrastructureDescription', { diff --git a/x-pack/legacy/plugins/infra/public/app.ts b/x-pack/legacy/plugins/infra/public/app.ts index 4b14e168eb768..7a13d3a59cc0d 100644 --- a/x-pack/legacy/plugins/infra/public/app.ts +++ b/x-pack/legacy/plugins/infra/public/app.ts @@ -9,7 +9,7 @@ // actually mount and run our application. Once in the NP this won't be an issue // as the NP will look for an export named "plugin" and run that from the index file. -import { npStart } from 'ui/new_platform'; +import { npStart, npSetup } from 'ui/new_platform'; import { PluginInitializerContext } from 'kibana/public'; import chrome from 'ui/chrome'; // @ts-ignore @@ -50,5 +50,7 @@ const checkForRoot = () => { }; checkForRoot().then(() => { - plugin({} as PluginInitializerContext).start(core, plugins, __LEGACY); + const pluginInstance = plugin({} as PluginInitializerContext); + pluginInstance.setup(npSetup.core, { home: npSetup.plugins.home }); + pluginInstance.start(core, plugins, __LEGACY); }); diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index dc6eabb325d16..f483f2b1b3f57 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -12,7 +12,7 @@ import { } from '@elastic/eui'; import React from 'react'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; import { composeStateUpdaters } from '../../utils/typed_react'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; value: string; autoFocus?: boolean; 'aria-label'?: string; diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx index 79b18f5888bd5..689eb47f289c2 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx @@ -8,14 +8,14 @@ import { EuiIcon } from '@elastic/eui'; import { transparentize } from 'polished'; import React from 'react'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; interface Props { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: autocomplete.QuerySuggestion; + suggestion: QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx index ab6949e2f1d06..839e40e057c9a 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx @@ -26,6 +26,8 @@ import { MetricsExplorerChartOptions as MetricsExplorerChartOptionsComponent } f import { SavedViewsToolbarControls } from '../saved_views/toolbar_control'; import { MetricExplorerViewState } from '../../pages/infrastructure/metrics_explorer/use_metric_explorer_state'; import { metricsExplorerViewSavedObjectType } from '../../../common/saved_objects/metrics_explorer_view'; +import { useKibanaUiSetting } from '../../utils/use_kibana_ui_setting'; +import { mapKibanaQuickRangesToDatePickerRanges } from '../../utils/map_timepicker_quickranges_to_datepicker_ranges'; interface Props { derivedIndexPattern: IIndexPattern; @@ -59,6 +61,8 @@ export const MetricsExplorerToolbar = ({ onViewStateChange, }: Props) => { const isDefaultOptions = options.aggregation === 'avg' && options.metrics.length === 0; + const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges'); + const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges); return ( @@ -134,6 +138,7 @@ export const MetricsExplorerToolbar = ({ end={timeRange.to} onTimeChange={({ start, end }) => onTimeChange(start, end)} onRefresh={onRefresh} + commonlyUsedRanges={commonlyUsedRanges} /> diff --git a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx index c92e2ecec9261..8188517ba7617 100644 --- a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx @@ -6,14 +6,14 @@ import React from 'react'; import { npStart } from 'ui/new_platform'; -import { autocomplete, IIndexPattern } from 'src/plugins/data/public'; +import { QuerySuggestion, IIndexPattern } from 'src/plugins/data/public'; import { RendererFunction } from '../utils/typed_react'; interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -25,7 +25,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< diff --git a/x-pack/legacy/plugins/infra/public/feature_catalogue_entry.ts b/x-pack/legacy/plugins/infra/public/feature_catalogue_entry.ts new file mode 100644 index 0000000000000..6442083234f2c --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/feature_catalogue_entry.ts @@ -0,0 +1,41 @@ +/* + * 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 { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +const APP_ID = 'infra'; + +export const featureCatalogueEntries = { + metrics: { + id: 'infraops', + title: i18n.translate('xpack.infra.registerFeatures.infraOpsTitle', { + defaultMessage: 'Metrics', + }), + description: i18n.translate('xpack.infra.registerFeatures.infraOpsDescription', { + defaultMessage: + 'Explore infrastructure metrics and logs for common servers, containers, and services.', + }), + icon: 'metricsApp', + path: `/app/${APP_ID}#infrastructure`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }, + logs: { + id: 'infralogging', + title: i18n.translate('xpack.infra.registerFeatures.logsTitle', { + defaultMessage: 'Logs', + }), + description: i18n.translate('xpack.infra.registerFeatures.logsDescription', { + defaultMessage: + 'Stream logs in real time or scroll through historical views in a console-like experience.', + }), + icon: 'logsApp', + path: `/app/${APP_ID}#logs`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }, +}; diff --git a/x-pack/legacy/plugins/infra/public/legacy_register_feature.ts b/x-pack/legacy/plugins/infra/public/legacy_register_feature.ts new file mode 100644 index 0000000000000..7b10a1e062f75 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/legacy_register_feature.ts @@ -0,0 +1,15 @@ +/* + * 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 { npSetup } from 'ui/new_platform'; +import { featureCatalogueEntries } from './feature_catalogue_entry'; + +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register(featureCatalogueEntries.metrics); +home.featureCatalogue.register(featureCatalogueEntries.logs); diff --git a/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts b/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts index 78594afcc8ada..f438b65794653 100644 --- a/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts +++ b/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart, PluginInitializerContext } from 'kibana/public'; +import { CoreStart, CoreSetup, PluginInitializerContext } from 'kibana/public'; import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import ApolloClient from 'apollo-client'; import { ApolloLink } from 'apollo-link'; @@ -13,12 +13,23 @@ import { startApp } from './apps/start_app'; import { InfraFrontendLibs } from './lib/lib'; import introspectionQueryResultData from './graphql/introspection.json'; import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api'; +import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; +import { featureCatalogueEntries } from './feature_catalogue_entry'; type ClientPlugins = any; type LegacyDeps = any; +interface InfraPluginSetupDependencies { + home: HomePublicPluginSetup; +} export class Plugin { constructor(context: PluginInitializerContext) {} + + setup(core: CoreSetup, { home }: InfraPluginSetupDependencies) { + home.featureCatalogue.register(featureCatalogueEntries.metrics); + home.featureCatalogue.register(featureCatalogueEntries.logs); + } + start(core: CoreStart, plugins: ClientPlugins, __LEGACY: LegacyDeps) { startApp(this.composeLibs(core, plugins, __LEGACY), core, plugins); } diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx index 624a2bb4a6f0f..91e25fd8ef585 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx @@ -3,6 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../utils/use_kibana_ui_setting', () => ({ + _esModule: true, + useKibanaUiSetting: jest.fn(() => [ + [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + ], + ]), +})); import React from 'react'; import { MetricsTimeControls } from './time_controls'; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx index d181aa37f59aa..1546966c10a1e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx @@ -5,9 +5,11 @@ */ import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; +import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges'; interface MetricsTimeControlsProps { currentTimeRange: MetricsTimeInput; @@ -19,41 +21,58 @@ interface MetricsTimeControlsProps { onRefresh: () => void; } -export class MetricsTimeControls extends React.Component { - public render() { - const { currentTimeRange, isLiveStreaming, refreshInterval } = this.props; - return ( - - - - ); - } - - private handleTimeChange = ({ start, end }: OnTimeChangeProps) => { - this.props.onChangeTimeRange({ - from: start, - to: end, - interval: '>=1m', - }); - }; - - private handleRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps) => { - if (isPaused) { - this.props.setAutoReload(false); - } else { - this.props.setRefreshInterval(refreshInterval); - this.props.setAutoReload(true); - } - }; -} +export const MetricsTimeControls = (props: MetricsTimeControlsProps) => { + const [timepickerQuickRanges] = useKibanaUiSetting('timepicker:quickRanges'); + const { + onChangeTimeRange, + onRefresh, + currentTimeRange, + isLiveStreaming, + refreshInterval, + setAutoReload, + setRefreshInterval, + } = props; + + const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges); + + const handleTimeChange = useCallback( + ({ start, end }: OnTimeChangeProps) => { + onChangeTimeRange({ + from: start, + to: end, + interval: '>=1m', + }); + }, + [onChangeTimeRange] + ); + + const handleRefreshChange = useCallback( + ({ isPaused, refreshInterval: _refreshInterval }: OnRefreshChangeProps) => { + if (isPaused) { + setAutoReload(false); + } else { + setRefreshInterval(_refreshInterval); + setAutoReload(true); + } + }, + [setAutoReload, setRefreshInterval] + ); + + return ( + + + + ); +}; const MetricsTimeControlsContainer = euiStyled.div` max-width: 750px; diff --git a/x-pack/legacy/plugins/infra/public/register_feature.ts b/x-pack/legacy/plugins/infra/public/register_feature.ts deleted file mode 100644 index bf56db77e360f..0000000000000 --- a/x-pack/legacy/plugins/infra/public/register_feature.ts +++ /dev/null @@ -1,43 +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 { I18nServiceType } from '@kbn/i18n/angular'; -import { - FeatureCatalogueCategory, - FeatureCatalogueRegistryProvider, -} from 'ui/registry/feature_catalogue'; - -const APP_ID = 'infra'; - -FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({ - id: 'infraops', - title: i18n('xpack.infra.registerFeatures.infraOpsTitle', { - defaultMessage: 'Metrics', - }), - description: i18n('xpack.infra.registerFeatures.infraOpsDescription', { - defaultMessage: - 'Explore infrastructure metrics and logs for common servers, containers, and services.', - }), - icon: 'metricsApp', - path: `/app/${APP_ID}#infrastructure`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, -})); - -FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({ - id: 'infralogging', - title: i18n('xpack.infra.registerFeatures.logsTitle', { - defaultMessage: 'Logs', - }), - description: i18n('xpack.infra.registerFeatures.logsDescription', { - defaultMessage: - 'Stream logs in real time or scroll through historical views in a console-like experience.', - }), - icon: 'logsApp', - path: `/app/${APP_ID}#logs`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, -})); diff --git a/x-pack/legacy/plugins/infra/public/utils/map_timepicker_quickranges_to_datepicker_ranges.ts b/x-pack/legacy/plugins/infra/public/utils/map_timepicker_quickranges_to_datepicker_ranges.ts new file mode 100644 index 0000000000000..68fac1ef6c084 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/utils/map_timepicker_quickranges_to_datepicker_ranges.ts @@ -0,0 +1,19 @@ +/* + * 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 { EuiSuperDatePickerCommonRange } from '@elastic/eui'; +import { TimePickerQuickRange } from './use_kibana_ui_setting'; + +export const mapKibanaQuickRangesToDatePickerRanges = ( + timepickerQuickRanges: TimePickerQuickRange[] | undefined +): EuiSuperDatePickerCommonRange[] => + timepickerQuickRanges + ? timepickerQuickRanges.map(r => ({ + start: r.from, + end: r.to, + label: r.display, + })) + : []; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts index ce39a31c0fc3f..b3697db81fb6e 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts @@ -25,7 +25,27 @@ import useObservable from 'react-use/lib/useObservable'; * Unlike the `useState`, it doesn't give type guarantees for the value, * because the underlying `UiSettingsClient` doesn't support that. */ -export const useKibanaUiSetting = (key: string, defaultValue?: any) => { + +export interface TimePickerQuickRange { + from: string; + to: string; + display: string; +} + +export function useKibanaUiSetting( + key: 'timepicker:quickRanges', + defaultValue?: TimePickerQuickRange[] +): [ + TimePickerQuickRange[], + (key: 'timepicker:quickRanges', value: TimePickerQuickRange[]) => Promise +]; + +export function useKibanaUiSetting( + key: string, + defaultValue?: any +): [any, (key: string, value: any) => Promise]; + +export function useKibanaUiSetting(key: string, defaultValue?: any) { const uiSettingsClient = npSetup.core.uiSettings; const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [ @@ -41,4 +61,4 @@ export const useKibanaUiSetting = (key: string, defaultValue?: any) => { ]); return [uiSetting, setUiSetting]; -}; +} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx index 62e2e628c254f..46a8304cc395e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx @@ -11,7 +11,7 @@ import { FieldItem, FieldItemProps } from './field_item'; import { coreMock } from 'src/core/public/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { npStart } from 'ui/new_platform'; -import { FieldFormatsStart } from '../../../../../../src/plugins/data/public'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPattern } from './types'; jest.mock('ui/new_platform'); @@ -87,7 +87,7 @@ describe('IndexPattern Field Item', () => { getDefaultInstance: jest.fn(() => ({ convert: jest.fn((s: unknown) => JSON.stringify(s)), })), - } as unknown) as FieldFormatsStart; + } as unknown) as DataPublicPluginStart['fieldFormats']; }); it('should request field stats without a time field, if the index pattern has none', async () => { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index a9a48c46f5bd0..1e0fce9f538b4 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -9,7 +9,7 @@ import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { MetricConfig } from './types'; -import { fieldFormats } from '../../../../../../src/plugins/data/public'; +import { IFieldFormat } from '../../../../../../src/plugins/data/public'; function sampleArgs() { const data: LensMultiTable = { @@ -55,9 +55,7 @@ describe('metric_expression', () => { const { data, args } = sampleArgs(); expect( - shallow( - x as fieldFormats.FieldFormat} /> - ) + shallow( x as IFieldFormat} />) ).toMatchInlineSnapshot(` { x as fieldFormats.FieldFormat} + formatFactory={x => x as IFieldFormat} /> ) ).toMatchInlineSnapshot(` diff --git a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature/register_home_feature.js b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts old mode 100755 new mode 100644 similarity index 63% rename from x-pack/legacy/plugins/logstash/public/lib/register_home_feature/register_home_feature.js rename to x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts index ee26cea54f977..e943656120d5e --- a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature/register_home_feature.js +++ b/x-pack/legacy/plugins/logstash/public/lib/register_home_feature.ts @@ -4,20 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - import { i18n } from '@kbn/i18n'; +import { npSetup } from 'ui/new_platform'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { FeatureCatalogueCategory } from '../../../../../../src/plugins/home/public'; +// @ts-ignore +import { PLUGIN } from '../../common/constants'; + +const { + plugins: { home }, +} = npSetup; -FeatureCatalogueRegistryProvider.register($injector => { - const licenseService = $injector.get('logstashLicenseService'); - if (!licenseService.enableLinks) { - return; - } +const enableLinks = Boolean(xpackInfo.get(`features.${PLUGIN.ID}.enableLinks`)); - return { +if (enableLinks) { + home.featureCatalogue.register({ id: 'management_logstash', title: i18n.translate('xpack.logstash.homeFeature.logstashPipelinesTitle', { defaultMessage: 'Logstash Pipelines', @@ -29,5 +31,5 @@ FeatureCatalogueRegistryProvider.register($injector => { path: '/app/kibana#/management/logstash/pipelines', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, - }; -}); + }); +} diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.ts similarity index 98% rename from x-pack/legacy/plugins/maps/common/constants.js rename to x-pack/legacy/plugins/maps/common/constants.ts index 2570341aa5756..ab9a696fa3a17 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.ts @@ -33,7 +33,7 @@ export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`; -export function createMapPath(id) { +export function createMapPath(id: string) { return `${MAP_BASE_URL}/${id}`; } diff --git a/x-pack/legacy/plugins/maps/common/i18n_getters.js b/x-pack/legacy/plugins/maps/common/i18n_getters.ts similarity index 90% rename from x-pack/legacy/plugins/maps/common/i18n_getters.js rename to x-pack/legacy/plugins/maps/common/i18n_getters.ts index 578d0cd4780e9..0008a119f1c7c 100644 --- a/x-pack/legacy/plugins/maps/common/i18n_getters.js +++ b/x-pack/legacy/plugins/maps/common/i18n_getters.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; +import { $Values } from '@kbn/utility-types'; import { ES_SPATIAL_RELATIONS } from './constants'; export function getAppTitle() { @@ -26,7 +27,7 @@ export function getUrlLabel() { }); } -export function getEsSpatialRelationLabel(spatialRelation) { +export function getEsSpatialRelationLabel(spatialRelation: $Values) { switch (spatialRelation) { case ES_SPATIAL_RELATIONS.INTERSECTS: return i18n.translate('xpack.maps.common.esSpatialRelation.intersectsLabel', { @@ -40,6 +41,7 @@ export function getEsSpatialRelationLabel(spatialRelation) { return i18n.translate('xpack.maps.common.esSpatialRelation.withinLabel', { defaultMessage: 'within', }); + // @ts-ignore case ES_SPATIAL_RELATIONS.CONTAINS: return i18n.translate('xpack.maps.common.esSpatialRelation.containsLabel', { defaultMessage: 'contains', diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 4f679905fc352..247dc8115c5c3 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -54,7 +54,7 @@ export function maps(kibana) { }, embeddableFactories: ['plugins/maps/embeddable/map_embeddable_factory'], inspectorViews: ['plugins/maps/inspector/views/register_views'], - home: ['plugins/maps/register_feature'], + home: ['plugins/maps/legacy_register_feature'], styleSheetPaths: `${__dirname}/public/index.scss`, savedObjectSchemas: { 'maps-telemetry': { diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js index 9aa5947062c83..ec0ae4161b3f2 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js @@ -297,6 +297,7 @@ function createGeometryFilterWithMeta({ type: SPATIAL_FILTER_TYPE, negate: false, index: indexPatternId, + key: geoFieldName, alias: `${geoFieldName} ${relationLabel} ${geometryLabel}`, }; diff --git a/x-pack/legacy/plugins/maps/public/feature_catalogue_entry.ts b/x-pack/legacy/plugins/maps/public/feature_catalogue_entry.ts new file mode 100644 index 0000000000000..fdda76b4e1212 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/feature_catalogue_entry.ts @@ -0,0 +1,21 @@ +/* + * 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 { APP_ID, APP_ICON } from '../common/constants'; +import { getAppTitle } from '../common/i18n_getters'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +export const featureCatalogueEntry = { + id: APP_ID, + title: getAppTitle(), + description: i18n.translate('xpack.maps.feature.appDescription', { + defaultMessage: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service', + }), + icon: APP_ICON, + path: '/app/maps', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +}; diff --git a/x-pack/legacy/plugins/maps/public/legacy_register_feature.ts b/x-pack/legacy/plugins/maps/public/legacy_register_feature.ts new file mode 100644 index 0000000000000..00f788f267d4b --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/legacy_register_feature.ts @@ -0,0 +1,14 @@ +/* + * 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 { npSetup } from 'ui/new_platform'; +import { featureCatalogueEntry } from './feature_catalogue_entry'; + +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register(featureCatalogueEntry); diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index e5f765a11d219..e2af53d59671f 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -11,6 +11,9 @@ import { wrapInI18nContext } from 'ui/i18n'; import { MapListing } from './components/map_listing'; // @ts-ignore import { setLicenseId, setInspector } from './kibana_services'; +import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; +import { featureCatalogueEntry } from './feature_catalogue_entry'; /** * These are the interfaces with your public contracts. You should export these @@ -20,14 +23,20 @@ import { setLicenseId, setInspector } from './kibana_services'; export type MapsPluginSetup = ReturnType; export type MapsPluginStart = ReturnType; +interface MapsPluginSetupDependencies { + __LEGACY: any; + np: { + licensing?: LicensingPluginSetup; + home: HomePublicPluginSetup; + }; +} + /** @internal */ export class MapsPlugin implements Plugin { - public setup(core: any, plugins: any) { - const { - __LEGACY: { uiModules }, - np: { licensing }, - } = plugins; - + public setup( + core: any, + { __LEGACY: { uiModules }, np: { licensing, home } }: MapsPluginSetupDependencies + ) { uiModules .get('app/maps', ['ngRoute', 'react']) .directive('mapListing', function(reactDirective: any) { @@ -35,8 +44,10 @@ export class MapsPlugin implements Plugin { }); if (licensing) { - licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); + licensing.license$.subscribe(({ uid }) => setLicenseId(uid)); } + + home.featureCatalogue.register(featureCatalogueEntry); } public start(core: CoreStart, plugins: any) { diff --git a/x-pack/legacy/plugins/maps/public/register_feature.js b/x-pack/legacy/plugins/maps/public/register_feature.js deleted file mode 100644 index afd7fb061500d..0000000000000 --- a/x-pack/legacy/plugins/maps/public/register_feature.js +++ /dev/null @@ -1,27 +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 { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; -import { i18n } from '@kbn/i18n'; -import { APP_ID, APP_ICON } from '../common/constants'; -import { getAppTitle } from '../common/i18n_getters'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: APP_ID, - title: getAppTitle(), - description: i18n.translate('xpack.maps.feature.appDescription', { - defaultMessage: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service', - }), - icon: APP_ICON, - path: '/app/maps', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; -}); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx index 2cb58f9c9d81c..1e24bfec6de5e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx @@ -218,7 +218,10 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) } return ( - + @@ -337,6 +340,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) = React.memo( : searchError; return ( - + diff --git a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts b/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts index 115e7fe6ba434..92373bae4ea1d 100644 --- a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts +++ b/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts @@ -5,8 +5,8 @@ */ import { Request } from 'hapi'; +import { Space } from '../../../../../plugins/spaces/server'; import { LegacySpacesPlugin } from '../../../spaces'; -import { Space } from '../../../spaces/common/model/space'; interface GetActiveSpaceResponse { valid: boolean; diff --git a/x-pack/legacy/plugins/monitoring/.kibana-plugin-helpers.json b/x-pack/legacy/plugins/monitoring/.kibana-plugin-helpers.json deleted file mode 100644 index 8696ea78df3ca..0000000000000 --- a/x-pack/legacy/plugins/monitoring/.kibana-plugin-helpers.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "styleSheetToCompile": "public/index.scss" -} diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js deleted file mode 100644 index 25b88958c116f..0000000000000 --- a/x-pack/legacy/plugins/monitoring/index.js +++ /dev/null @@ -1,98 +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 { resolve } from 'path'; -import { config } from './config'; -import { deprecations } from './deprecations'; -import { getUiExports } from './ui_exports'; -import { Plugin } from './server/plugin'; -import { initInfraSource } from './server/lib/logs/init_infra_source'; -import { KIBANA_ALERTING_ENABLED } from './common/constants'; - -/** - * Invokes plugin modules to instantiate the Monitoring plugin for Kibana - * @param kibana {Object} Kibana plugin instance - * @return {Object} Monitoring UI Kibana plugin object - */ -const deps = ['kibana', 'elasticsearch', 'xpack_main']; -if (KIBANA_ALERTING_ENABLED) { - deps.push(...['alerting', 'actions']); -} -export const monitoring = kibana => - new kibana.Plugin({ - require: deps, - id: 'monitoring', - configPrefix: 'monitoring', - publicDir: resolve(__dirname, 'public'), - init(server) { - const configs = [ - 'monitoring.ui.enabled', - 'monitoring.kibana.collection.enabled', - 'monitoring.ui.max_bucket_size', - 'monitoring.ui.min_interval_seconds', - 'kibana.index', - 'monitoring.ui.show_license_expiration', - 'monitoring.ui.container.elasticsearch.enabled', - 'monitoring.ui.container.logstash.enabled', - 'monitoring.tests.cloud_detector.enabled', - 'monitoring.kibana.collection.interval', - 'monitoring.ui.elasticsearch.hosts', - 'monitoring.ui.elasticsearch', - 'monitoring.xpack_api_polling_frequency_millis', - 'server.uuid', - 'server.name', - 'server.host', - 'server.port', - 'monitoring.cluster_alerts.email_notifications.enabled', - 'monitoring.cluster_alerts.email_notifications.email_address', - 'monitoring.ui.ccs.enabled', - 'monitoring.ui.elasticsearch.logFetchCount', - 'monitoring.ui.logs.index', - ]; - - const serverConfig = server.config(); - const serverFacade = { - config: () => ({ - get: key => { - if (configs.includes(key)) { - return serverConfig.get(key); - } - throw `Unknown key '${key}'`; - }, - }), - injectUiAppVars: server.injectUiAppVars, - log: (...args) => server.log(...args), - logger: server.newPlatform.coreContext.logger, - getOSInfo: server.getOSInfo, - events: { - on: (...args) => server.events.on(...args), - }, - expose: (...args) => server.expose(...args), - route: (...args) => server.route(...args), - _hapi: server, - _kbnServer: this.kbnServer, - }; - const { usageCollection, licensing } = server.newPlatform.setup.plugins; - const plugins = { - xpack_main: server.plugins.xpack_main, - elasticsearch: server.plugins.elasticsearch, - infra: server.plugins.infra, - alerting: server.plugins.alerting, - usageCollection, - licensing, - }; - - const plugin = new Plugin(); - plugin.setup(serverFacade, plugins); - }, - config, - deprecations, - uiExports: getUiExports(), - postInit(server) { - const serverConfig = server.config(); - initInfraSource(serverConfig, server.plugins.infra); - }, - }); diff --git a/x-pack/legacy/plugins/monitoring/index.ts b/x-pack/legacy/plugins/monitoring/index.ts new file mode 100644 index 0000000000000..c596beb117971 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/index.ts @@ -0,0 +1,138 @@ +/* + * 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 { resolve } from 'path'; +import KbnServer, { Server } from 'src/legacy/server/kbn_server'; +import { + LegacyPluginApi, + LegacyPluginSpec, + LegacyPluginOptions, +} from 'src/legacy/plugin_discovery/types'; +import { KIBANA_ALERTING_ENABLED } from './common/constants'; + +// @ts-ignore +import { getUiExports } from './ui_exports'; +// @ts-ignore +import { config as configDefaults } from './config'; +// @ts-ignore +import { deprecations } from './deprecations'; +// @ts-ignore +import { Plugin } from './server/plugin'; +// @ts-ignore +import { initInfraSource } from './server/lib/logs/init_infra_source'; + +type InfraPlugin = any; // TODO +type PluginsSetup = any; // TODO +type LegacySetup = any; // TODO + +const deps = ['kibana', 'elasticsearch', 'xpack_main']; +if (KIBANA_ALERTING_ENABLED) { + deps.push(...['alerting', 'actions']); +} + +const validConfigOptions: string[] = [ + 'monitoring.ui.enabled', + 'monitoring.kibana.collection.enabled', + 'monitoring.ui.max_bucket_size', + 'monitoring.ui.min_interval_seconds', + 'kibana.index', + 'monitoring.ui.show_license_expiration', + 'monitoring.ui.container.elasticsearch.enabled', + 'monitoring.ui.container.logstash.enabled', + 'monitoring.tests.cloud_detector.enabled', + 'monitoring.kibana.collection.interval', + 'monitoring.ui.elasticsearch.hosts', + 'monitoring.ui.elasticsearch', + 'monitoring.xpack_api_polling_frequency_millis', + 'server.uuid', + 'server.name', + 'server.host', + 'server.port', + 'monitoring.cluster_alerts.email_notifications.enabled', + 'monitoring.cluster_alerts.email_notifications.email_address', + 'monitoring.ui.ccs.enabled', + 'monitoring.ui.elasticsearch.logFetchCount', + 'monitoring.ui.logs.index', +]; + +interface LegacyPluginOptionsWithKbnServer extends LegacyPluginOptions { + kbnServer?: KbnServer; +} + +/** + * Invokes plugin modules to instantiate the Monitoring plugin for Kibana + * @param kibana {Object} Kibana plugin instance + * @return {Object} Monitoring UI Kibana plugin object + */ +export const monitoring = (kibana: LegacyPluginApi): LegacyPluginSpec => { + return new kibana.Plugin({ + require: deps, + id: 'monitoring', + configPrefix: 'monitoring', + publicDir: resolve(__dirname, 'public'), + config: configDefaults, + uiExports: getUiExports(), + deprecations, + + init(server: Server) { + const serverConfig = server.config(); + const { getOSInfo, plugins, injectUiAppVars } = server as typeof server & { getOSInfo?: any }; + const log = (...args: Parameters) => server.log(...args); + const route = (...args: Parameters) => server.route(...args); + const expose = (...args: Parameters) => server.expose(...args); + const serverFacade = { + config: () => ({ + get: (key: string) => { + if (validConfigOptions.includes(key)) { + return serverConfig.get(key); + } + throw new Error(`Unknown key '${key}'`); + }, + }), + injectUiAppVars, + log, + logger: server.newPlatform.coreContext.logger, + getOSInfo, + events: { + on: (...args: Parameters) => server.events.on(...args), + }, + route, + expose, + _hapi: server, + _kbnServer: this.kbnServer, + }; + + const legacyPlugins = plugins as Partial & { infra?: InfraPlugin }; + const { xpack_main, elasticsearch, infra, alerting } = legacyPlugins; + const { + core: coreSetup, + plugins: { usageCollection, licensing }, + } = server.newPlatform.setup; + + const pluginsSetup: PluginsSetup = { + usageCollection, + licensing, + }; + + const __LEGACY: LegacySetup = { + ...serverFacade, + plugins: { + xpack_main, + elasticsearch, + infra, + alerting, + }, + }; + + new Plugin().setup(coreSetup, pluginsSetup, __LEGACY); + }, + + postInit(server: Server) { + const { infra } = server.plugins as Partial & { infra?: InfraPlugin }; + initInfraSource(server.config(), infra); + }, + } as Partial); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/beats/overview/overview.test.js b/x-pack/legacy/plugins/monitoring/public/components/beats/overview/overview.test.js index 4c96772826c98..1947f042b09b7 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/beats/overview/overview.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/beats/overview/overview.test.js @@ -14,6 +14,12 @@ jest.mock('../../', () => ({ MonitoringTimeseriesContainer: () => 'MonitoringTimeseriesContainer', })); +jest.mock('../../../np_imports/ui/chrome', () => { + return { + getBasePath: () => '', + }; +}); + import { BeatsOverview } from './overview'; describe('Overview', () => { diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js index 56eb52fa86235..d8a6f1ad6bd9e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js @@ -43,25 +43,34 @@ const props = { updateLegend: () => void 0, }; -describe('Test legends to toggle series: ', () => { +jest.mock('../../np_imports/ui/chrome', () => { + return { + getBasePath: () => '', + }; +}); + +// TODO: Skipping for now, seems flaky in New Platform (needs more investigation) +describe.skip('Test legends to toggle series: ', () => { const ids = props.series.map(item => item.id); - it('should toggle based on seriesToShow array', () => { - const component = shallow(); + describe('props.series: ', () => { + it('should toggle based on seriesToShow array', () => { + const component = shallow(); - const componentClass = component.instance(); + const componentClass = component.instance(); - const seriesA = componentClass.filterData(props.series, [ids[0]]); - expect(seriesA.length).to.be(1); - expect(seriesA[0].id).to.be(ids[0]); + const seriesA = componentClass.filterData(props.series, [ids[0]]); + expect(seriesA.length).to.be(1); + expect(seriesA[0].id).to.be(ids[0]); - const seriesB = componentClass.filterData(props.series, [ids[1]]); - expect(seriesB.length).to.be(1); - expect(seriesB[0].id).to.be(ids[1]); + const seriesB = componentClass.filterData(props.series, [ids[1]]); + expect(seriesB.length).to.be(1); + expect(seriesB[0].id).to.be(ids[1]); - const seriesAB = componentClass.filterData(props.series, ids); - expect(seriesAB.length).to.be(2); - expect(seriesAB[0].id).to.be(ids[0]); - expect(seriesAB[1].id).to.be(ids[1]); + const seriesAB = componentClass.filterData(props.series, ids); + expect(seriesAB.length).to.be(2); + expect(seriesAB[0].id).to.be(ids[0]); + expect(seriesAB[1].id).to.be(ids[1]); + }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/get_chart_options.js b/x-pack/legacy/plugins/monitoring/public/components/chart/get_chart_options.js index 9f5691fdacac7..6f26abeadb3a0 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/get_chart_options.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/get_chart_options.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import chrome from '../../np_imports/ui/chrome'; import { merge } from 'lodash'; import { CHART_LINE_COLOR, CHART_TEXT_COLOR } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/listing/listing.js index 7e88890ea9316..4cf74b3595730 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/listing/listing.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment, Component } from 'react'; -import chrome from 'ui/chrome'; +import chrome from 'plugins/monitoring/np_imports/ui/chrome'; import moment from 'moment'; import numeral from '@elastic/numeral'; import { capitalize, partial } from 'lodash'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js index 8806fc80f1122..17caa8429a275 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js @@ -8,6 +8,12 @@ import React from 'react'; import { shallow } from 'enzyme'; import { CcrShard } from './ccr_shard'; +jest.mock('../../../np_imports/ui/chrome', () => { + return { + getBasePath: () => '', + }; +}); + describe('CcrShard', () => { const props = { formattedLeader: 'leader on remote', diff --git a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js index 053130076fa77..df817df268de4 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js @@ -27,7 +27,7 @@ import { SetupModeBadge } from '../../setup_mode/badge'; import { KIBANA_SYSTEM_ID } from '../../../../common/constants'; import { ListingCallOut } from '../../setup_mode/listing_callout'; -const getColumns = (kbnUrl, scope, setupMode) => { +const getColumns = setupMode => { const columns = [ { name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', { @@ -68,11 +68,7 @@ const getColumns = (kbnUrl, scope, setupMode) => { return (
{ - scope.$evalAsync(() => { - kbnUrl.changePath(`/kibana/instances/${kibana.kibana.uuid}`); - }); - }} + href={`#/kibana/instances/${kibana.kibana.uuid}`} data-test-subj={`kibanaLink-${name}`} > {name} diff --git a/x-pack/legacy/plugins/monitoring/public/components/license/index.js b/x-pack/legacy/plugins/monitoring/public/components/license/index.js index 75534da6fbef3..d43896d5f8d84 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/license/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/license/index.js @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { LicenseStatus, AddLicense } from 'plugins/xpack_main/components'; import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import chrome from 'plugins/monitoring/np_imports/ui/chrome'; const licenseManagement = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js index c67a708c4f98e..926f5cdda26a7 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js @@ -5,14 +5,14 @@ */ import React, { PureComponent } from 'react'; import { capitalize } from 'lodash'; -import chrome from 'ui/chrome'; +import chrome from '../../np_imports/ui/chrome'; import { EuiBasicTable, EuiTitle, EuiSpacer, EuiText, EuiCallOut, EuiLink } from '@elastic/eui'; import { INFRA_SOURCE_ID } from '../../../common/constants'; import { formatDateTimeLocal } from '../../../common/formatting'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Reason } from './reason'; -import { capabilities } from 'ui/capabilities'; +import { capabilities } from '../../np_imports/ui/capabilities'; const columnTimestampTitle = i18n.translate('xpack.monitoring.logs.listing.timestampTitle', { defaultMessage: 'Timestamp', diff --git a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js index 450484fdafbb3..63af8b208fbec 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.test.js @@ -8,14 +8,14 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Logs } from './logs'; -jest.mock('ui/chrome', () => { +jest.mock('../../np_imports/ui/chrome', () => { return { getBasePath: () => '', }; }); jest.mock( - 'ui/capabilities', + '../../np_imports/ui/capabilities', () => ({ capabilities: { get: () => ({ logs: { show: true } }), diff --git a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/no_data.test.js b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/no_data.test.js index 82c46711e8ca9..81a412a680bc6 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/no_data.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/no_data.test.js @@ -10,6 +10,12 @@ import { NoData } from '../'; const enabler = {}; +jest.mock('../../../np_imports/ui/chrome', () => { + return { + getBasePath: () => '', + }; +}); + describe('NoData', () => { test('should show text next to the spinner while checking a setting', () => { const component = renderWithIntl( diff --git a/x-pack/legacy/plugins/monitoring/public/directives/__tests__/fixtures/providers.js b/x-pack/legacy/plugins/monitoring/public/directives/__tests__/fixtures/providers.js deleted file mode 100644 index 6779c6f7f0671..0000000000000 --- a/x-pack/legacy/plugins/monitoring/public/directives/__tests__/fixtures/providers.js +++ /dev/null @@ -1,4 +0,0 @@ -import { uiModules } from 'ui/modules'; - -const uiModule = uiModules.get('monitoring/directives', []); -uiModule.service('sessionTimeout', () => {}); diff --git a/x-pack/legacy/plugins/monitoring/public/directives/beats/beat/index.js b/x-pack/legacy/plugins/monitoring/public/directives/beats/beat/index.js index 1248c9c3f4b49..c86315fc03482 100644 --- a/x-pack/legacy/plugins/monitoring/public/directives/beats/beat/index.js +++ b/x-pack/legacy/plugins/monitoring/public/directives/beats/beat/index.js @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { Beat } from 'plugins/monitoring/components/beats/beat'; import { I18nContext } from 'ui/i18n'; diff --git a/x-pack/legacy/plugins/monitoring/public/directives/beats/overview/index.js b/x-pack/legacy/plugins/monitoring/public/directives/beats/overview/index.js index a30bcac79193a..fb78b6a2e0300 100644 --- a/x-pack/legacy/plugins/monitoring/public/directives/beats/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/directives/beats/overview/index.js @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { BeatsOverview } from 'plugins/monitoring/components/beats/overview'; import { I18nContext } from 'ui/i18n'; diff --git a/x-pack/legacy/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js b/x-pack/legacy/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js index 4880337f13eec..8f35bd599ac49 100644 --- a/x-pack/legacy/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js +++ b/x-pack/legacy/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js @@ -9,7 +9,7 @@ import numeral from '@elastic/numeral'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; import { MachineLearningJobStatusIcon } from 'plugins/monitoring/components/elasticsearch/ml_job_listing/status_icon'; import { LARGE_ABBREVIATED, LARGE_BYTES } from '../../../../common/formatting'; diff --git a/x-pack/legacy/plugins/monitoring/public/directives/main/index.js b/x-pack/legacy/plugins/monitoring/public/directives/main/index.js index cbd93ab3902e9..2505f651d9803 100644 --- a/x-pack/legacy/plugins/monitoring/public/directives/main/index.js +++ b/x-pack/legacy/plugins/monitoring/public/directives/main/index.js @@ -8,12 +8,12 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { EuiSelect, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { shortenPipelineHash } from '../../../common/formatting'; -import 'ui/directives/kbn_href'; import { getSetupModeState, initSetupModeState } from '../../lib/setup_mode'; +import { Subscription } from 'rxjs'; const setOptions = controller => { if ( @@ -76,6 +76,24 @@ export class MonitoringMainController { this.inApm = false; } + addTimerangeObservers = () => { + this.subscriptions = new Subscription(); + + const refreshIntervalUpdated = () => { + const { value: refreshInterval, pause: isPaused } = timefilter.getRefreshInterval(); + this.datePicker.onRefreshChange({ refreshInterval, isPaused }, true); + }; + + const timeUpdated = () => { + this.datePicker.onTimeUpdate({ dateRange: timefilter.getTime() }, true); + }; + + this.subscriptions.add( + timefilter.getRefreshIntervalUpdate$().subscribe(refreshIntervalUpdated) + ); + this.subscriptions.add(timefilter.getTimeUpdate$().subscribe(timeUpdated)); + }; + dropdownLoadedHandler() { this.pipelineDropdownElement = document.querySelector('#dropdown-elm'); setOptions(this); @@ -122,22 +140,25 @@ export class MonitoringMainController { this.datePicker = { timeRange: timefilter.getTime(), refreshInterval: timefilter.getRefreshInterval(), - onRefreshChange: ({ isPaused, refreshInterval }) => { + onRefreshChange: ({ isPaused, refreshInterval }, skipSet = false) => { this.datePicker.refreshInterval = { pause: isPaused, value: refreshInterval, }; - - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : this.datePicker.refreshInterval.value, - }); + if (!skipSet) { + timefilter.setRefreshInterval({ + pause: isPaused, + value: refreshInterval ? refreshInterval : this.datePicker.refreshInterval.value, + }); + } }, - onTimeUpdate: ({ dateRange }) => { + onTimeUpdate: ({ dateRange }, skipSet = false) => { this.datePicker.timeRange = { ...dateRange, }; - timefilter.setTime(dateRange); + if (!skipSet) { + timefilter.setTime(dateRange); + } this._executorService.cancel(); this._executorService.run(); }, @@ -175,7 +196,7 @@ export class MonitoringMainController { } } -const uiModule = uiModules.get('plugins/monitoring/directives', []); +const uiModule = uiModules.get('monitoring/directives', []); uiModule.directive('monitoringMain', (breadcrumbs, license, kbnUrl, $injector) => { const $executor = $injector.get('$executor'); @@ -187,6 +208,7 @@ uiModule.directive('monitoringMain', (breadcrumbs, license, kbnUrl, $injector) = controllerAs: 'monitoringMain', bindToController: true, link(scope, _element, attributes, controller) { + controller.addTimerangeObservers(); initSetupModeState(scope, $injector, () => { controller.setup(getSetupObj()); }); @@ -226,12 +248,11 @@ uiModule.directive('monitoringMain', (breadcrumbs, license, kbnUrl, $injector) = Object.keys(setupObj.attributes).forEach(key => { attributes.$observe(key, () => controller.setup(getSetupObj())); }); - scope.$on( - '$destroy', - () => - controller.pipelineDropdownElement && - unmountComponentAtNode(controller.pipelineDropdownElement) - ); + scope.$on('$destroy', () => { + controller.pipelineDropdownElement && + unmountComponentAtNode(controller.pipelineDropdownElement); + controller.subscriptions && controller.subscriptions.unsubscribe(); + }); scope.$watch('pageData.versions', versions => { controller.pipelineVersions = versions; setOptions(controller); diff --git a/x-pack/legacy/plugins/monitoring/public/filters/index.js b/x-pack/legacy/plugins/monitoring/public/filters/index.js index 90f6efd38ed78..a67770ff50dc8 100644 --- a/x-pack/legacy/plugins/monitoring/public/filters/index.js +++ b/x-pack/legacy/plugins/monitoring/public/filters/index.js @@ -5,7 +5,7 @@ */ import { capitalize } from 'lodash'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { formatNumber, formatMetric } from 'plugins/monitoring/lib/format_number'; import { extractIp } from 'plugins/monitoring/lib/extract_ip'; diff --git a/x-pack/legacy/plugins/monitoring/public/monitoring.js b/x-pack/legacy/plugins/monitoring/public/legacy.ts similarity index 50% rename from x-pack/legacy/plugins/monitoring/public/monitoring.js rename to x-pack/legacy/plugins/monitoring/public/legacy.ts index 99a4174169bfd..293b6ac7bd821 100644 --- a/x-pack/legacy/plugins/monitoring/public/monitoring.js +++ b/x-pack/legacy/plugins/monitoring/public/legacy.ts @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import uiRoutes from 'ui/routes'; -import chrome from 'ui/chrome'; -import 'ui/kbn_top_nav'; -import 'ui/directives/storage'; -import 'ui/autoload/all'; import 'plugins/monitoring/filters'; import 'plugins/monitoring/services/clusters'; import 'plugins/monitoring/services/features'; @@ -18,27 +13,15 @@ import 'plugins/monitoring/services/title'; import 'plugins/monitoring/services/breadcrumbs'; import 'plugins/monitoring/directives/all'; import 'plugins/monitoring/views/all'; +import { npSetup, npStart } from '../public/np_imports/legacy_imports'; +import { plugin } from './np_ready'; +import { localApplicationService } from '../../../../../src/legacy/core_plugins/kibana/public/local_application_service'; -const uiSettings = chrome.getUiSettingsClient(); - -// default timepicker default to the last hour -uiSettings.overrideLocalDefault( - 'timepicker:timeDefaults', - JSON.stringify({ - from: 'now-1h', - to: 'now', - mode: 'quick', - }) -); - -// default autorefresh to active and refreshing every 10 seconds -uiSettings.overrideLocalDefault( - 'timepicker:refreshIntervalDefaults', - JSON.stringify({ - pause: false, - value: 10000, - }) -); - -// Enable Angular routing -uiRoutes.enable(); +const pluginInstance = plugin({} as any); +pluginInstance.setup(npSetup.core, npSetup.plugins); +pluginInstance.start(npStart.core, { + ...npStart.plugins, + __LEGACY: { + localApplicationService, + }, +}); diff --git a/x-pack/legacy/plugins/monitoring/public/lib/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/lib/get_page_data.js index 08dd7043ce695..ae04b2d8791fa 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from './ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector, api) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/lib/route_init.js b/x-pack/legacy/plugins/monitoring/public/lib/route_init.js index ba7610cf13f94..97a55303dae67 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/route_init.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/route_init.js @@ -27,8 +27,8 @@ export function routeInitProvider(Private, monitoringClusters, globalState, lice return ( monitoringClusters(clusterUuid, undefined, codePaths) // Set the clusters collection and current cluster in globalState - .then(async clusters => { - const inSetupMode = await isInSetupMode(); + .then(clusters => { + const inSetupMode = isInSetupMode(); const cluster = getClusterFromClusters(clusters, globalState); if (!cluster && !inSetupMode) { return kbnUrl.redirect('/no-data'); diff --git a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js index 4a2b470f04c72..765909f0aa251 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + coreMock, + overlayServiceMock, + notificationServiceMock, +} from '../../../../../../src/core/public/mocks'; + let toggleSetupMode; let initSetupModeState; let getSetupModeState; @@ -55,10 +61,70 @@ function waitForSetupModeData(action) { process.nextTick(action); } -function setModules() { - jest.resetModules(); +function mockFilterManager() { + let subscriber; + let filters = []; + return { + getUpdates$: () => ({ + subscribe: ({ next }) => { + subscriber = next; + return jest.fn(); + }, + }), + setFilters: newFilters => { + filters = newFilters; + subscriber(); + }, + getFilters: () => filters, + removeAll: () => { + filters = []; + subscriber(); + }, + }; +} + +const pluginData = { + query: { + filterManager: mockFilterManager(), + timefilter: { + timefilter: { + getTime: jest.fn(() => ({ from: 'now-1h', to: 'now' })), + setTime: jest.fn(), + }, + }, + }, +}; + +function setModulesAndMocks(isOnCloud = false) { + jest.clearAllMocks().resetModules(); injectorModulesMock.globalState.inSetupMode = false; + jest.doMock('ui/new_platform', () => ({ + npSetup: { + plugins: { + cloud: isOnCloud ? { cloudId: 'test', isCloudEnabled: true } : {}, + uiActions: { + registerAction: jest.fn(), + attachAction: jest.fn(), + }, + }, + core: { + ...coreMock.createSetup(), + notifications: notificationServiceMock.createStartContract(), + }, + }, + npStart: { + plugins: { + data: pluginData, + navigation: { ui: {} }, + }, + core: { + ...coreMock.createStart(), + overlays: overlayServiceMock.createStartContract(), + }, + }, + })); + const setupMode = require('./setup_mode'); toggleSetupMode = setupMode.toggleSetupMode; initSetupModeState = setupMode.initSetupModeState; @@ -69,17 +135,7 @@ function setModules() { describe('setup_mode', () => { beforeEach(async () => { - jest.doMock('ui/new_platform', () => ({ - npSetup: { - plugins: { - cloud: { - cloudId: undefined, - isCloudEnabled: false, - }, - }, - }, - })); - setModules(); + setModulesAndMocks(); }); describe('setup', () => { @@ -125,16 +181,6 @@ describe('setup_mode', () => { it('should not fetch data if on cloud', async done => { const addDanger = jest.fn(); - jest.doMock('ui/new_platform', () => ({ - npSetup: { - plugins: { - cloud: { - cloudId: 'test', - isCloudEnabled: true, - }, - }, - }, - })); data = { _meta: { hasPermissions: true, @@ -145,7 +191,7 @@ describe('setup_mode', () => { addDanger, }, })); - setModules(); + setModulesAndMocks(true); initSetupModeState(angularStateMock.scope, angularStateMock.injector); await toggleSetupMode(true); waitForSetupModeData(() => { @@ -171,7 +217,7 @@ describe('setup_mode', () => { hasPermissions: false, }, }; - setModules(); + setModulesAndMocks(); initSetupModeState(angularStateMock.scope, angularStateMock.injector); await toggleSetupMode(true); waitForSetupModeData(() => { diff --git a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx index d805c10247b2e..7b081b79d6acd 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx +++ b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { render } from 'react-dom'; import { get, contains } from 'lodash'; -import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import { npSetup } from 'ui/new_platform'; import { PluginsSetup } from 'ui/new_platform/new_platform'; +import chrome from '../np_imports/ui/chrome'; import { CloudSetup } from '../../../../../plugins/cloud/public'; import { ajaxErrorHandlersProvider } from './ajax_error_handler'; import { SetupModeEnterButton } from '../components/setup_mode/enter_button'; @@ -207,12 +207,12 @@ export const initSetupModeState = async ($scope: any, $injector: any, callback?: } }; -export const isInSetupMode = async () => { +export const isInSetupMode = () => { if (setupModeState.enabled) { return true; } - const $injector = angularState.injector || (await chrome.dangerouslyGetActiveInjector()); + const $injector = angularState.injector || chrome.dangerouslyGetActiveInjector(); const globalState = $injector.get('globalState'); return globalState.inSetupMode; }; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/angular_config.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/angular_config.ts new file mode 100644 index 0000000000000..d1849d9247985 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/angular_config.ts @@ -0,0 +1,157 @@ +/* + * 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 { + ICompileProvider, + IHttpProvider, + IHttpService, + ILocationProvider, + IModule, + IRootScopeService, +} from 'angular'; +import $ from 'jquery'; +import _, { cloneDeep, forOwn, get, set } from 'lodash'; +import * as Rx from 'rxjs'; +import { CoreStart, LegacyCoreStart } from 'kibana/public'; + +const isSystemApiRequest = (request: any) => + Boolean(request && request.headers && !!request.headers['kbn-system-api']); + +export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { + const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); + + forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { + if (name !== undefined) { + // The legacy platform modifies some of these values, clone to an unfrozen object. + angularModule.value(name, cloneDeep(val)); + } + }); + + angularModule + .value('kbnVersion', newPlatform.injectedMetadata.getKibanaVersion()) + .value('buildNum', legacyMetadata.buildNum) + .value('buildSha', legacyMetadata.buildSha) + .value('serverName', legacyMetadata.serverName) + .value('esUrl', getEsUrl(newPlatform)) + .value('uiCapabilities', newPlatform.application.capabilities) + .config(setupCompileProvider(newPlatform)) + .config(setupLocationProvider()) + .config($setupXsrfRequestInterceptor(newPlatform)) + .run(capture$httpLoadingCount(newPlatform)) + .run($setupUICapabilityRedirect(newPlatform)); +}; + +const getEsUrl = (newPlatform: CoreStart) => { + const a = document.createElement('a'); + a.href = newPlatform.http.basePath.prepend('/elasticsearch'); + const protocolPort = /https/.test(a.protocol) ? 443 : 80; + const port = a.port || protocolPort; + return { + host: a.hostname, + port, + protocol: a.protocol, + pathname: a.pathname, + }; +}; + +const setupCompileProvider = (newPlatform: LegacyCoreStart) => ( + $compileProvider: ICompileProvider +) => { + if (!newPlatform.injectedMetadata.getLegacyMetadata().devMode) { + $compileProvider.debugInfoEnabled(false); + } +}; + +const setupLocationProvider = () => ($locationProvider: ILocationProvider) => { + $locationProvider.html5Mode({ + enabled: false, + requireBase: false, + rewriteLinks: false, + }); + + $locationProvider.hashPrefix(''); +}; + +const $setupXsrfRequestInterceptor = (newPlatform: LegacyCoreStart) => { + const version = newPlatform.injectedMetadata.getLegacyMetadata().version; + + // Configure jQuery prefilter + $.ajaxPrefilter(({ kbnXsrfToken = true }: any, originalOptions, jqXHR) => { + if (kbnXsrfToken) { + jqXHR.setRequestHeader('kbn-version', version); + } + }); + + return ($httpProvider: IHttpProvider) => { + // Configure $httpProvider interceptor + $httpProvider.interceptors.push(() => { + return { + request(opts) { + const { kbnXsrfToken = true } = opts as any; + if (kbnXsrfToken) { + set(opts, ['headers', 'kbn-version'], version); + } + return opts; + }, + }; + }); + }; +}; + +/** + * Injected into angular module by ui/chrome angular integration + * and adds a root-level watcher that will capture the count of + * active $http requests on each digest loop and expose the count to + * the core.loadingCount api + * @param {Angular.Scope} $rootScope + * @param {HttpService} $http + * @return {undefined} + */ +const capture$httpLoadingCount = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $http: IHttpService +) => { + newPlatform.http.addLoadingCountSource( + new Rx.Observable(observer => { + const unwatch = $rootScope.$watch(() => { + const reqs = $http.pendingRequests || []; + observer.next(reqs.filter(req => !isSystemApiRequest(req)).length); + }); + + return unwatch; + }) + ); +}; + +/** + * integrates with angular to automatically redirect to home if required + * capability is not met + */ +const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); + // this feature only works within kibana app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isKibanaAppRoute) { + return; + } + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: { requireUICapability: boolean } } = {}) => { + if (!route || !route.requireUICapability) { + return; + } + + if (!get(newPlatform.application.capabilities, route.requireUICapability)) { + $injector.get('kbnUrl').change('/home'); + event.preventDefault(); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/index.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/index.ts new file mode 100644 index 0000000000000..8fd8d170bbb40 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/index.ts @@ -0,0 +1,48 @@ +/* + * 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 angular, { IModule } from 'angular'; + +import { AppMountContext, LegacyCoreStart } from 'kibana/public'; + +// @ts-ignore TODO: change to absolute path +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; +// @ts-ignore TODO: change to absolute path +import chrome from 'plugins/monitoring/np_imports/ui/chrome'; +// @ts-ignore TODO: change to absolute path +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; +// @ts-ignore TODO: change to absolute path +import { registerTimefilterWithGlobalState } from 'plugins/monitoring/np_imports/ui/timefilter'; +import { configureAppAngularModule } from './angular_config'; + +import { localAppModule, appModuleName } from './modules'; + +export class AngularApp { + private injector?: angular.auto.IInjectorService; + + constructor({ core }: AppMountContext, { element }: { element: HTMLElement }) { + uiModules.addToModule(); + const app: IModule = localAppModule(core); + app.config(($routeProvider: any) => { + $routeProvider.eagerInstantiationEnabled(false); + uiRoutes.addToProvider($routeProvider); + }); + configureAppAngularModule(app, core as LegacyCoreStart); + registerTimefilterWithGlobalState(app); + const appElement = document.createElement('div'); + appElement.setAttribute('style', 'height: 100%'); + appElement.innerHTML = '
'; + this.injector = angular.bootstrap(appElement, [appModuleName]); + chrome.setInjector(this.injector); + angular.element(element).append(appElement); + } + + public destroy = () => { + if (this.injector) { + this.injector.get('$rootScope').$destroy(); + } + }; +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts new file mode 100644 index 0000000000000..2acb6031c6773 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts @@ -0,0 +1,162 @@ +/* + * 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 angular, { IWindowService } from 'angular'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; + +import { AppMountContext } from 'kibana/public'; +import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; + +import { + GlobalStateProvider, + StateManagementConfigProvider, + AppStateProvider, + EventsProvider, + PersistedState, + createTopNavDirective, + createTopNavHelper, + KbnUrlProvider, + RedirectWhenMissingProvider, + npStart, +} from '../legacy_imports'; + +// @ts-ignore +import { PromiseServiceCreator } from './providers/promises'; +// @ts-ignore +import { PrivateProvider } from './providers/private'; + +type IPrivate = (provider: (...injectable: any[]) => T) => T; + +export const appModuleName = 'monitoring'; +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +export const localAppModule = (core: AppMountContext['core']) => { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalStorage(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(npStart.plugins.navigation); + createHrefModule(core); + + const appModule = angular.module(appModuleName, [ + ...thirdPartyAngularDependencies, + 'monitoring/Config', + 'monitoring/I18n', + 'monitoring/Private', + 'monitoring/PersistedState', + 'monitoring/TopNav', + 'monitoring/State', + 'monitoring/Storage', + 'monitoring/href', + 'monitoring/services', + 'monitoring/filters', + 'monitoring/directives', + ]); + return appModule; +}; + +function createLocalStateModule() { + angular + .module('monitoring/State', [ + 'monitoring/Private', + 'monitoring/Config', + 'monitoring/KbnUrl', + 'monitoring/Promise', + 'monitoring/PersistedState', + ]) + .factory('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('monitoring/PersistedState', ['monitoring/Private', 'monitoring/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: string) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('monitoring/KbnUrl', ['monitoring/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('monitoring/Config', ['monitoring/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('monitoring/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalStorage() { + angular + .module('monitoring/Storage', []) + .service('localStorage', ($window: IWindowService) => new Storage($window.localStorage)) + .service('sessionStorage', ($window: IWindowService) => new Storage($window.sessionStorage)) + .service('sessionTimeout', () => {}); +} + +function createLocalPrivateModule() { + angular.module('monitoring/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule({ ui }: any) { + angular + .module('monitoring/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(ui)); +} + +function createLocalI18nModule() { + angular + .module('monitoring/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createHrefModule(core: AppMountContext['core']) { + const name: string = 'kbnHref'; + angular.module('monitoring/href', []).directive(name, () => { + return { + restrict: 'A', + link: { + pre: (_$scope, _$el, $attr) => { + $attr.$observe(name, val => { + if (val) { + $attr.$set('href', core.http.basePath.prepend(val as string)); + } + }); + }, + }, + }; + }); +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/private.js b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/private.js new file mode 100644 index 0000000000000..6eae978b828b3 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/private.js @@ -0,0 +1,196 @@ +/* + * 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. + */ + +/** + * # `Private()` + * Private module loader, used to merge angular and require js dependency styles + * by allowing a require.js module to export a single provider function that will + * create a value used within an angular application. This provider can declare + * angular dependencies by listing them as arguments, and can be require additional + * Private modules. + * + * ## Define a private module provider: + * ```js + * export default function PingProvider($http) { + * this.ping = function () { + * return $http.head('/health-check'); + * }; + * }; + * ``` + * + * ## Require a private module: + * ```js + * export default function ServerHealthProvider(Private, Promise) { + * let ping = Private(require('ui/ping')); + * return { + * check: Promise.method(function () { + * let attempts = 0; + * return (function attempt() { + * attempts += 1; + * return ping.ping() + * .catch(function (err) { + * if (attempts < 3) return attempt(); + * }) + * }()) + * .then(function () { + * return true; + * }) + * .catch(function () { + * return false; + * }); + * }) + * } + * }; + * ``` + * + * # `Private.stub(provider, newInstance)` + * `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now. + * + * ```js + * beforeEach(inject(function ($injector, Private) { + * Private.stub( + * // since this module just exports a function, we need to change + * // what Private returns in order to modify it's behavior + * require('ui/agg_response/hierarchical/_build_split'), + * sinon.stub().returns(fakeSplit) + * ); + * })); + * ``` + * + * # `Private.swap(oldProvider, newProvider)` + * This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance. + * Pass the module you want to swap out, and the one it should be replaced with, then profit. + * + * Note: even though this example shows `swap()` being called in a config + * function, it can be called from anywhere. It is particularly useful + * in this scenario though. + * + * ```js + * beforeEach(module('kibana', function (PrivateProvider) { + * PrivateProvider.swap( + * function StubbedRedirectProvider($decorate) { + * // $decorate is a function that will instantiate the original module when called + * return sinon.spy($decorate()); + * } + * ); + * })); + * ``` + * + * @param {[type]} prov [description] + */ +import _ from 'lodash'; + +const nextId = _.partial(_.uniqueId, 'privateProvider#'); + +function name(fn) { + return ( + fn.name || + fn + .toString() + .split('\n') + .shift() + ); +} + +export function PrivateProvider() { + const provider = this; + + // one cache/swaps per Provider + const cache = {}; + const swaps = {}; + + // return the uniq id for this function + function identify(fn) { + if (typeof fn !== 'function') { + throw new TypeError('Expected private module "' + fn + '" to be a function'); + } + + if (fn.$$id) return fn.$$id; + else return (fn.$$id = nextId()); + } + + provider.stub = function(fn, instance) { + cache[identify(fn)] = instance; + return instance; + }; + + provider.swap = function(fn, prov) { + const id = identify(fn); + swaps[id] = prov; + }; + + provider.$get = [ + '$injector', + function PrivateFactory($injector) { + // prevent circular deps by tracking where we came from + const privPath = []; + const pathToString = function() { + return privPath.map(name).join(' -> '); + }; + + // call a private provider and return the instance it creates + function instantiate(prov, locals) { + if (~privPath.indexOf(prov)) { + throw new Error( + 'Circular reference to "' + + name(prov) + + '"' + + ' found while resolving private deps: ' + + pathToString() + ); + } + + privPath.push(prov); + + const context = {}; + let instance = $injector.invoke(prov, context, locals); + if (!_.isObject(instance)) instance = context; + + privPath.pop(); + return instance; + } + + // retrieve an instance from cache or create and store on + function get(id, prov, $delegateId, $delegateProv) { + if (cache[id]) return cache[id]; + + let instance; + + if ($delegateId != null && $delegateProv != null) { + instance = instantiate(prov, { + $decorate: _.partial(get, $delegateId, $delegateProv), + }); + } else { + instance = instantiate(prov); + } + + return (cache[id] = instance); + } + + // main api, get the appropriate instance for a provider + function Private(prov) { + let id = identify(prov); + let $delegateId; + let $delegateProv; + + if (swaps[id]) { + $delegateId = id; + $delegateProv = prov; + + prov = swaps[$delegateId]; + id = identify(prov); + } + + return get(id, prov, $delegateId, $delegateProv); + } + + Private.stub = provider.stub; + Private.swap = provider.swap; + + return Private; + }, + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/promises.js b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/promises.js new file mode 100644 index 0000000000000..22adccaf3db7f --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/providers/promises.js @@ -0,0 +1,116 @@ +/* + * 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'; + +export function PromiseServiceCreator($q, $timeout) { + function Promise(fn) { + if (typeof this === 'undefined') + throw new Error('Promise constructor must be called with "new"'); + + const defer = $q.defer(); + try { + fn(defer.resolve, defer.reject); + } catch (e) { + defer.reject(e); + } + return defer.promise; + } + + Promise.all = Promise.props = $q.all; + Promise.resolve = function(val) { + const defer = $q.defer(); + defer.resolve(val); + return defer.promise; + }; + Promise.reject = function(reason) { + const defer = $q.defer(); + defer.reject(reason); + return defer.promise; + }; + Promise.cast = $q.when; + Promise.delay = function(ms) { + return $timeout(_.noop, ms); + }; + Promise.method = function(fn) { + return function() { + const args = Array.prototype.slice.call(arguments); + return Promise.try(fn, args, this); + }; + }; + Promise.nodeify = function(promise, cb) { + promise.then(function(val) { + cb(void 0, val); + }, cb); + }; + Promise.map = function(arr, fn) { + return Promise.all( + arr.map(function(i, el, list) { + return Promise.try(fn, [i, el, list]); + }) + ); + }; + Promise.each = function(arr, fn) { + const queue = arr.slice(0); + let i = 0; + return (function next() { + if (!queue.length) return arr; + return Promise.try(fn, [arr.shift(), i++]).then(next); + })(); + }; + Promise.is = function(obj) { + // $q doesn't create instances of any constructor, promises are just objects with a then function + // https://github.com/angular/angular.js/blob/58f5da86645990ef984353418cd1ed83213b111e/src/ng/q.js#L335 + return obj && typeof obj.then === 'function'; + }; + Promise.halt = _.once(function() { + const promise = new Promise(() => {}); + promise.then = _.constant(promise); + promise.catch = _.constant(promise); + return promise; + }); + Promise.try = function(fn, args, ctx) { + if (typeof fn !== 'function') { + return Promise.reject(new TypeError('fn must be a function')); + } + + let value; + + if (Array.isArray(args)) { + try { + value = fn.apply(ctx, args); + } catch (e) { + return Promise.reject(e); + } + } else { + try { + value = fn.call(ctx, args); + } catch (e) { + return Promise.reject(e); + } + } + + return Promise.resolve(value); + }; + Promise.fromNode = function(takesCbFn) { + return new Promise(function(resolve, reject) { + takesCbFn(function(err, ...results) { + if (err) reject(err); + else if (results.length > 1) resolve(results); + else resolve(results[0]); + }); + }); + }; + Promise.race = function(iterable) { + return new Promise((resolve, reject) => { + for (const i of iterable) { + Promise.resolve(i).then(resolve, reject); + } + }); + }; + + return Promise; +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts new file mode 100644 index 0000000000000..012cbc77ce9c8 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * Last remaining 'ui/*' imports that will eventually be shimmed with their np alternatives + */ + +export { npSetup, npStart } from 'ui/new_platform'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +export { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/capabilities.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/capabilities.ts new file mode 100644 index 0000000000000..5aff302501401 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/capabilities.ts @@ -0,0 +1,8 @@ +/* + * 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 { npStart } from '../legacy_imports'; +export const capabilities = { get: () => npStart.core.application.capabilities }; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/chrome.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/chrome.ts new file mode 100644 index 0000000000000..f0c5bacabecbf --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/chrome.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import angular from 'angular'; +import { npStart, npSetup } from '../legacy_imports'; + +type OptionalInjector = void | angular.auto.IInjectorService; + +class Chrome { + private injector?: OptionalInjector; + + public setInjector = (injector: OptionalInjector): void => void (this.injector = injector); + public dangerouslyGetActiveInjector = (): OptionalInjector => this.injector; + + public getBasePath = (): string => npStart.core.http.basePath.get(); + + public getInjected = (name?: string, defaultValue?: any): string | unknown => { + const { getInjectedVar, getInjectedVars } = npSetup.core.injectedMetadata; + return name ? getInjectedVar(name, defaultValue) : getInjectedVars(); + }; + + public get breadcrumbs() { + const set = (...args: any[]) => npStart.core.chrome.setBreadcrumbs.apply(this, args as any); + return { set }; + } +} + +const chrome = new Chrome(); + +export default chrome; // eslint-disable-line import/no-default-export diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/modules.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/modules.ts new file mode 100644 index 0000000000000..70201a7906110 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/modules.ts @@ -0,0 +1,55 @@ +/* + * 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 angular from 'angular'; + +type PrivateProvider = (...args: any) => any; +interface Provider { + name: string; + provider: PrivateProvider; +} + +class Modules { + private _services: Provider[] = []; + private _filters: Provider[] = []; + private _directives: Provider[] = []; + + public get = (_name: string, _dep?: string[]) => { + return this; + }; + + public service = (...args: any) => { + this._services.push(args); + }; + + public filter = (...args: any) => { + this._filters.push(args); + }; + + public directive = (...args: any) => { + this._directives.push(args); + }; + + public addToModule = () => { + angular.module('monitoring/services', []); + angular.module('monitoring/filters', []); + angular.module('monitoring/directives', []); + + this._services.forEach(args => { + angular.module('monitoring/services').service.apply(null, args as any); + }); + + this._filters.forEach(args => { + angular.module('monitoring/filters').filter.apply(null, args as any); + }); + + this._directives.forEach(args => { + angular.module('monitoring/directives').directive.apply(null, args as any); + }); + }; +} + +export const uiModules = new Modules(); diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/routes.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/routes.ts new file mode 100644 index 0000000000000..22da56a8d184a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/routes.ts @@ -0,0 +1,39 @@ +/* + * 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. + */ + +type RouteObject = [string, any]; +interface Redirect { + redirectTo: string; +} + +class Routes { + private _routes: RouteObject[] = []; + private _redirect?: Redirect; + + public when = (...args: RouteObject) => { + const [, routeOptions] = args; + routeOptions.reloadOnSearch = false; + this._routes.push(args); + return this; + }; + + public otherwise = (redirect: Redirect) => { + this._redirect = redirect; + return this; + }; + + public addToProvider = ($routeProvider: any) => { + this._routes.forEach(args => { + $routeProvider.when.apply(this, args); + }); + + if (this._redirect) { + $routeProvider.otherwise(this._redirect); + } + }; +} +const uiRoutes = new Routes(); +export default uiRoutes; // eslint-disable-line import/no-default-export diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/timefilter.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/timefilter.ts new file mode 100644 index 0000000000000..e28699bd126b9 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/timefilter.ts @@ -0,0 +1,31 @@ +/* + * 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 { IModule, IRootScopeService } from 'angular'; +import { npStart, registerTimefilterWithGlobalStateFactory } from '../legacy_imports'; + +const { + core: { uiSettings }, +} = npStart; +export const { timefilter } = npStart.plugins.data.query.timefilter; + +uiSettings.overrideLocalDefault( + 'timepicker:refreshIntervalDefaults', + JSON.stringify({ value: 10000, pause: false }) +); +uiSettings.overrideLocalDefault( + 'timepicker:timeDefaults', + JSON.stringify({ from: 'now-1h', to: 'now' }) +); + +export const registerTimefilterWithGlobalState = (app: IModule) => { + app.run((globalState: any, $rootScope: IRootScopeService) => { + globalState.fetch(); + globalState.$inheritedGlobalState = true; + globalState.save(); + registerTimefilterWithGlobalStateFactory(timefilter, globalState, $rootScope); + }); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/ui/utils.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/utils.ts new file mode 100644 index 0000000000000..0ebae88dba760 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/ui/utils.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IScope } from 'angular'; +import * as Rx from 'rxjs'; + +/** + * Subscribe to an observable at a $scope, ensuring that the digest cycle + * is run for subscriber hooks and routing errors to fatalError if not handled. + */ +export const subscribeWithScope = ( + $scope: IScope, + observable: Rx.Observable, + observer?: Rx.PartialObserver +) => { + return observable.subscribe({ + next(value) { + if (observer && observer.next) { + $scope.$applyAsync(() => observer.next!(value)); + } + }, + error(error) { + $scope.$applyAsync(() => { + if (observer && observer.error) { + observer.error(error); + } else { + throw new Error( + `Uncaught error in subscribeWithScope(): ${ + error ? error.stack || error.message : error + }` + ); + } + }); + }, + complete() { + if (observer && observer.complete) { + $scope.$applyAsync(() => observer.complete!()); + } + }, + }); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/np_ready/index.ts b/x-pack/legacy/plugins/monitoring/public/np_ready/index.ts new file mode 100644 index 0000000000000..80848c497c370 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_ready/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { MonitoringPlugin } from './plugin'; + +export function plugin(ctx: PluginInitializerContext) { + return new MonitoringPlugin(ctx); +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_ready/plugin.ts b/x-pack/legacy/plugins/monitoring/public/np_ready/plugin.ts new file mode 100644 index 0000000000000..5598a7a51cf42 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/np_ready/plugin.ts @@ -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. + */ + +import { App, CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; + +export class MonitoringPlugin implements Plugin { + constructor(ctx: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: any) { + const app: App = { + id: 'monitoring', + title: 'Monitoring', + mount: async (context, params) => { + const { AngularApp } = await import('../np_imports/angular'); + const monitoringApp = new AngularApp(context, params); + return monitoringApp.destroy; + }, + }; + + core.application.register(app); + } + + public start(core: CoreStart, plugins: any) {} + public stop() {} +} diff --git a/x-pack/legacy/plugins/monitoring/public/register_feature.js b/x-pack/legacy/plugins/monitoring/public/register_feature.js deleted file mode 100644 index f275662bfb077..0000000000000 --- a/x-pack/legacy/plugins/monitoring/public/register_feature.js +++ /dev/null @@ -1,30 +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 chrome from 'ui/chrome'; -import { i18n } from '@kbn/i18n'; -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -if (chrome.getInjected('monitoringUiEnabled')) { - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'monitoring', - title: i18n.translate('xpack.monitoring.monitoringTitle', { - defaultMessage: 'Monitoring', - }), - description: i18n.translate('xpack.monitoring.monitoringDescription', { - defaultMessage: 'Track the real-time health and performance of your Elastic Stack.', - }), - icon: 'monitoringApp', - path: '/app/monitoring', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; - }); -} diff --git a/x-pack/legacy/plugins/monitoring/public/register_feature.ts b/x-pack/legacy/plugins/monitoring/public/register_feature.ts new file mode 100644 index 0000000000000..9b72e01a19394 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/register_feature.ts @@ -0,0 +1,30 @@ +/* + * 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 chrome from 'ui/chrome'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +const { + plugins: { home }, +} = npSetup; + +if (chrome.getInjected('monitoringUiEnabled')) { + home.featureCatalogue.register({ + id: 'monitoring', + title: i18n.translate('xpack.monitoring.monitoringTitle', { + defaultMessage: 'Monitoring', + }), + description: i18n.translate('xpack.monitoring.monitoringDescription', { + defaultMessage: 'Track the real-time health and performance of your Elastic Stack.', + }), + icon: 'monitoringApp', + path: '/app/monitoring', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); +} diff --git a/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js b/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js index 0ed4dbf52edf2..2c4d49716406c 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js +++ b/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { executorProvider } from '../executor_provider'; import Bluebird from 'bluebird'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; describe('$executor service', () => { let scope; diff --git a/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs.js b/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs.js index fee359956ada6..d0fe600386307 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs.js +++ b/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { breadcrumbsProvider } from './breadcrumbs_provider'; const uiModule = uiModules.get('monitoring/breadcrumbs', []); uiModule.service('breadcrumbs', breadcrumbsProvider); diff --git a/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs_provider.js b/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs_provider.js index d35dfca6d6727..7917606a5bc8e 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs_provider.js +++ b/x-pack/legacy/plugins/monitoring/public/services/breadcrumbs_provider.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import chrome from 'plugins/monitoring/np_imports/ui/chrome'; import { i18n } from '@kbn/i18n'; // Helper for making objects to use in a link element diff --git a/x-pack/legacy/plugins/monitoring/public/services/clusters.js b/x-pack/legacy/plugins/monitoring/public/services/clusters.js index 7d612abc0e4fd..40d6fa59228f8 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/clusters.js +++ b/x-pack/legacy/plugins/monitoring/public/services/clusters.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants'; function formatClusters(clusters) { diff --git a/x-pack/legacy/plugins/monitoring/public/services/executor.js b/x-pack/legacy/plugins/monitoring/public/services/executor.js index 70f162948638b..5004cd0238012 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/executor.js +++ b/x-pack/legacy/plugins/monitoring/public/services/executor.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { executorProvider } from './executor_provider'; const uiModule = uiModules.get('monitoring/executor', []); uiModule.service('$executor', executorProvider); diff --git a/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js b/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js index b2192496ed272..4a0551fa5af11 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js +++ b/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { timefilter } from 'ui/timefilter'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; +import { subscribeWithScope } from 'plugins/monitoring/np_imports/ui/utils'; import { Subscription } from 'rxjs'; export function executorProvider(Promise, $timeout) { const queue = []; diff --git a/x-pack/legacy/plugins/monitoring/public/services/features.js b/x-pack/legacy/plugins/monitoring/public/services/features.js index 06fb69902c013..e2357ef08d7df 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/features.js +++ b/x-pack/legacy/plugins/monitoring/public/services/features.js @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; const uiModule = uiModules.get('monitoring/features', []); uiModule.service('features', function($window) { diff --git a/x-pack/legacy/plugins/monitoring/public/services/license.js b/x-pack/legacy/plugins/monitoring/public/services/license.js index a9e40d8950004..94078b799fdf1 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/license.js +++ b/x-pack/legacy/plugins/monitoring/public/services/license.js @@ -5,7 +5,7 @@ */ import { contains } from 'lodash'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { ML_SUPPORTED_LICENSES } from '../../common/constants'; const uiModule = uiModules.get('monitoring/license', []); diff --git a/x-pack/legacy/plugins/monitoring/public/services/title.js b/x-pack/legacy/plugins/monitoring/public/services/title.js index f6ebfee1f5f11..442f4fb5b4029 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/title.js +++ b/x-pack/legacy/plugins/monitoring/public/services/title.js @@ -6,7 +6,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { uiModules } from 'ui/modules'; +import { uiModules } from 'plugins/monitoring/np_imports/ui/modules'; import { docTitle } from 'ui/doc_title'; const uiModule = uiModules.get('monitoring/title', []); diff --git a/x-pack/legacy/plugins/monitoring/public/views/__tests__/base_controller.js b/x-pack/legacy/plugins/monitoring/public/views/__tests__/base_controller.js index ae84e2d0eaeb4..6c3c73a35601c 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/__tests__/base_controller.js +++ b/x-pack/legacy/plugins/monitoring/public/views/__tests__/base_controller.js @@ -7,7 +7,7 @@ import { spy, stub } from 'sinon'; import expect from '@kbn/expect'; import { MonitoringViewBaseController } from '../'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { PromiseWithCancel, Status } from '../../../common/cancel_promise'; /* diff --git a/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.js b/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.js index cb1bc6c8ff030..a0cfc79f001ca 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/access_denied/index.js @@ -5,8 +5,8 @@ */ import { noop } from 'lodash'; -import uiRoutes from 'ui/routes'; -import uiChrome from 'ui/chrome'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; +import uiChrome from 'plugins/monitoring/np_imports/ui/chrome'; import template from './index.html'; const tryPrivilege = ($http, kbnUrl) => { diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 1bfc76b766457..7c065a78a8af9 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -8,12 +8,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { render } from 'react-dom'; import { find, get } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import template from './index.html'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { I18nContext } from 'ui/i18n'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { Alerts } from '../../components/alerts'; import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/legacy/plugins/monitoring/public/views/apm/instance/index.js index 7e2da1c93e4fa..4d0f858d28117 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/apm/instance/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/apm/instance/index.js @@ -13,7 +13,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { find, get } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/legacy/plugins/monitoring/public/views/apm/instances/index.js index 04eff6fd98e9b..317879063b6e5 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/apm/instances/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/apm/instances/index.js @@ -7,7 +7,7 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { find } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { ApmServerInstances } from '../../../components/apm/instances'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/apm/overview/index.js index 24c4444766eb5..e6562f428d2a0 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/apm/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/apm/overview/index.js @@ -6,7 +6,7 @@ import React from 'react'; import { find } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../base_controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/base_controller.js b/x-pack/legacy/plugins/monitoring/public/views/base_controller.js index ac1475ea62099..25b4d97177a98 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/legacy/plugins/monitoring/public/views/base_controller.js @@ -9,7 +9,7 @@ import moment from 'moment'; import { render, unmountComponentAtNode } from 'react-dom'; import { getPageData } from '../lib/get_page_data'; import { PageLoading } from 'plugins/monitoring/components'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; import { PromiseWithCancel } from '../../common/cancel_promise'; import { updateSetupModeData, getSetupModeState } from '../lib/setup_mode'; @@ -188,15 +188,20 @@ export class MonitoringViewBaseController { } renderReact(component) { + const renderElement = document.getElementById(this.reactNodeId); + if (!renderElement) { + console.warn(`"#${this.reactNodeId}" element has not been added to the DOM yet`); + return; + } if (this._isDataInitialized === false) { render( , - document.getElementById(this.reactNodeId) + renderElement ); } else { - render(component, document.getElementById(this.reactNodeId)); + render(component, renderElement); } } diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/beat/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/beats/beat/get_page_data.js index 1c57d846902ec..7e77e93d52fe8 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/beat/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/beat/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/legacy/plugins/monitoring/public/views/beats/beat/index.js index 276d2ec4c949b..b3fad1b4cc3cb 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/beat/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/beat/index.js @@ -6,7 +6,7 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/listing/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/beats/listing/get_page_data.js index b4359b2842247..1838011dee652 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/listing/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/listing/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/legacy/plugins/monitoring/public/views/beats/listing/index.js index f11b4751f4c6c..48848007c9c27 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/listing/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/listing/index.js @@ -6,7 +6,7 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/overview/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/beats/overview/get_page_data.js index ff07729c4d1e9..a3b120b277b94 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/overview/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/overview/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/beats/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/beats/overview/index.js index 9e814c2345fa0..aea62d5c7f78f 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/beats/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/beats/overview/index.js @@ -6,7 +6,7 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/legacy/plugins/monitoring/public/views/cluster/listing/index.js index 55020baeafa7b..1c8500caa48af 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/cluster/listing/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/cluster/listing/index.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { I18nContext } from 'ui/i18n'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js index e7107860d61fa..e1777b8ed7b49 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js @@ -7,7 +7,7 @@ import React, { Fragment } from 'react'; import { isEmpty } from 'lodash'; import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js index a5d9556eaf963..83dd24209dfe3 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/index.js index 2083fefcd9aa3..cf51347842f4a 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/index.js @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { getPageData } from './get_page_data'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js index 020122fac2e7f..22ca094d28b07 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js index c67267a76acc8..ff35f7f743f66 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { getPageData } from './get_page_data'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js index 0d8ec6383f60d..4fc439b4e0123 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { AdvancedIndex } from '../../../../components/elasticsearch/index/advanced'; import { I18nContext } from 'ui/i18n'; import { MonitoringViewBaseController } from '../../../base_controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/index.js index 9951650ec2bf7..bbeef8294a897 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/index/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels'; import { indicesByNodes } from '../../../components/elasticsearch/shard_allocation/transformers/indices_by_nodes'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/indices/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/indices/index.js index 4177f23caa6a7..f1d96557b0c1c 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/indices/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/indices/index.js @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { find } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { ElasticsearchIndices } from '../../../components'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js index b18530564849c..1943b580f7a75 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js index cbbed06d71b1a..5e66a4147ab70 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js @@ -6,7 +6,7 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js index 888f337c4fa7b..2bbdf604d00ce 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; import { AdvancedNode } from '../../../../components/elasticsearch/node/advanced'; import { MonitoringViewBaseController } from '../../../base_controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js index 0e2e57371a764..0d9e0b25eacd0 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/index.js index 0ef74feb64fab..fa76222d78e2d 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/node/index.js @@ -10,7 +10,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { partial } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { getPageData } from './get_page_data'; import template from './index.html'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/nodes/index.js index d201e2cc8b5e9..a9a6774d4c883 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/nodes/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/nodes/index.js @@ -7,8 +7,8 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { find } from 'lodash'; -import uiRoutes from 'ui/routes'; -import { timefilter } from 'ui/timefilter'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import template from './index.html'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/overview/index.js index 64e57c9e8e8e3..475c0fc494857 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/elasticsearch/overview/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { ElasticsearchOverviewController } from './controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/kibana/instance/index.js b/x-pack/legacy/plugins/monitoring/public/views/kibana/instance/index.js index 0dbfb048864e9..6535bd7410445 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/kibana/instance/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/kibana/instance/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { get } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { EuiPage, EuiPageBody, diff --git a/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/get_page_data.js index ec6f3800c99c8..4f8d7fa20d332 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/index.js index e08313c6313e7..51a7e033bd0d6 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/kibana/instances/index.js @@ -5,7 +5,7 @@ */ import React, { Fragment } from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/kibana/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/kibana/overview/index.js index f0cdb2a8b1fc9..0705e3b7f270b 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/kibana/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/kibana/overview/index.js @@ -8,12 +8,12 @@ * Kibana Overview */ import React from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { MonitoringTimeseriesContainer } from '../../../components/chart'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { EuiPage, EuiPageBody, diff --git a/x-pack/legacy/plugins/monitoring/public/views/license/controller.js b/x-pack/legacy/plugins/monitoring/public/views/license/controller.js index e6c1bd330e4c7..dcd3ca76ceffd 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/license/controller.js +++ b/x-pack/legacy/plugins/monitoring/public/views/license/controller.js @@ -8,11 +8,11 @@ import { get, find } from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import chrome from 'ui/chrome'; +import chrome from 'plugins/monitoring/np_imports/ui/chrome'; import { formatDateTimeLocal } from '../../../common/formatting'; import { MANAGEMENT_BASE_PATH } from 'plugins/xpack_main/components'; import { License } from 'plugins/monitoring/components'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; const REACT_NODE_ID = 'licenseReact'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/license/index.js b/x-pack/legacy/plugins/monitoring/public/views/license/index.js index ab93fef0f834a..e0796c85d8f85 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/license/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/license/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; import { LicenseViewController } from './controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/loading/index.js b/x-pack/legacy/plugins/monitoring/public/views/loading/index.js index fd4c9a0c37311..0488683845a7d 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/loading/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/loading/index.js @@ -7,7 +7,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { PageLoading } from 'plugins/monitoring/components'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { I18nContext } from 'ui/i18n'; import template from './index.html'; import { CODE_PATH_LICENSE } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/advanced/index.js index 45246e52b1a00..29cf4839eff94 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/advanced/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/advanced/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { MonitoringViewBaseController } from '../../../base_controller'; import { DetailStatus } from 'plugins/monitoring/components/logstash/detail_status'; import { diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/index.js index bf31556c2898b..f1777d1e46ef0 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/index.js @@ -9,11 +9,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { DetailStatus } from 'plugins/monitoring/components/logstash/detail_status'; import { EuiPage, diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js index 7bfcddf8f283a..017988b70bdd4 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js @@ -10,12 +10,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { isPipelineMonitoringSupportedInVersion } from 'plugins/monitoring/lib/logstash/pipelines'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { MonitoringViewBaseEuiTableController } from '../../../'; import { I18nContext } from 'ui/i18n'; import { PipelineListing } from '../../../../components/logstash/pipeline_listing/pipeline_listing'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/get_page_data.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/get_page_data.js index 9ec247b8f1199..d476f6ba5143e 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/get_page_data.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/get_page_data.js @@ -5,7 +5,7 @@ */ import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; export function getPageData($injector) { const $http = $injector.get('$http'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/index.js index c4a33de5a4a64..30f851b2a7534 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/nodes/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { getPageData } from './get_page_data'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/overview/index.js index c73d82b70f63d..f41f54555952e 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/overview/index.js @@ -8,11 +8,11 @@ * Logstash Overview */ import React from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; import { Overview } from '../../../components/logstash/overview'; import { MonitoringViewBaseController } from '../../base_controller'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipeline/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipeline/index.js index 8e16d183950f4..11cb8516847c8 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipeline/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipeline/index.js @@ -8,7 +8,7 @@ * Logstash Node Pipeline View */ import React from 'react'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import moment from 'moment'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js index 03cf7383d1d02..75a18000c14dd 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js @@ -7,12 +7,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { find } from 'lodash'; -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { isPipelineMonitoringSupportedInVersion } from 'plugins/monitoring/lib/logstash/pipelines'; import template from './index.html'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter'; import { I18nContext } from 'ui/i18n'; import { PipelineListing } from '../../../components/logstash/pipeline_listing/pipeline_listing'; import { MonitoringViewBaseEuiTableController } from '../..'; diff --git a/x-pack/legacy/plugins/monitoring/public/views/no_data/index.js b/x-pack/legacy/plugins/monitoring/public/views/no_data/index.js index 953cae5024806..edade513e5ab2 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/no_data/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/no_data/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import uiRoutes from 'ui/routes'; +import uiRoutes from 'plugins/monitoring/np_imports/ui/routes'; import template from './index.html'; import { NoDataController } from './controller'; diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index 50e5319a0f526..c2aed7365f3af 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -19,11 +19,22 @@ import { getLicenseExpiration } from './alerts/license_expiration'; import { parseElasticsearchConfig } from './es_client/parse_elasticsearch_config'; export class Plugin { - setup(core, plugins) { - const kbnServer = core._kbnServer; - const config = core.config(); - const usageCollection = plugins.usageCollection; - const licensing = plugins.licensing; + setup(_coreSetup, pluginsSetup, __LEGACY) { + const { + plugins, + _kbnServer: kbnServer, + log, + logger, + getOSInfo, + _hapi: hapiServer, + events, + expose, + config: monitoringConfig, + injectUiAppVars, + } = __LEGACY; + const config = monitoringConfig(); + + const { usageCollection, licensing } = pluginsSetup; registerMonitoringCollection(); /* * Register collector objects for stats to show up in the APIs @@ -31,10 +42,10 @@ export class Plugin { registerCollectors(usageCollection, { elasticsearchPlugin: plugins.elasticsearch, kbnServerConfig: kbnServer.config, - log: core.log, + log, config, - getOSInfo: core.getOSInfo, - hapiServer: core._hapi, + getOSInfo, + hapiServer, }); /* @@ -57,18 +68,18 @@ export class Plugin { if (uiEnabled) { await instantiateClient({ - log: core.log, - events: core.events, + log, + events, elasticsearchConfig, elasticsearchPlugin: plugins.elasticsearch, }); // Instantiate the dedicated ES client await initMonitoringXpackInfo({ config, - log: core.log, + log, xpackMainPlugin: plugins.xpack_main, - expose: core.expose, + expose, }); // Route handlers depend on this for xpackInfo - await requireUIRoutes(core); + await requireUIRoutes(__LEGACY); } }); @@ -99,7 +110,7 @@ export class Plugin { const bulkUploader = initBulkUploader({ elasticsearchPlugin: plugins.elasticsearch, config, - log: core.log, + log, kbnServerStatus: kbnServer.status, kbnServerVersion: kbnServer.version, }); @@ -121,18 +132,18 @@ export class Plugin { } }); } else if (!kibanaCollectionEnabled) { - core.log( + log( ['info', LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG], 'Internal collection for Kibana monitoring is disabled per configuration.' ); } - core.injectUiAppVars('monitoring', () => { - const config = core.config(); + injectUiAppVars('monitoring', () => { return { maxBucketSize: config.get('monitoring.ui.max_bucket_size'), minIntervalSeconds: config.get('monitoring.ui.min_interval_seconds'), kbnIndex: config.get('kibana.index'), + monitoringUiEnabled: config.get('monitoring.ui.enabled'), showLicenseExpiration: config.get('monitoring.ui.show_license_expiration'), showCgroupMetricsElasticsearch: config.get('monitoring.ui.container.elasticsearch.enabled'), showCgroupMetricsLogstash: config.get('monitoring.ui.container.logstash.enabled'), // Note, not currently used, but see https://github.com/elastic/x-pack-kibana/issues/1559 part 2 @@ -159,11 +170,11 @@ export class Plugin { } function getLogger(contexts) { - return core.logger.get('plugins', LOGGING_TAG, ...contexts); + return logger.get('plugins', LOGGING_TAG, ...contexts); } plugins.alerting.setup.registerType( getLicenseExpiration( - core._hapi, + hapiServer, getMonitoringCluster, getLogger, config.get('xpack.monitoring.ccs.enabled') diff --git a/x-pack/legacy/plugins/monitoring/ui_exports.js b/x-pack/legacy/plugins/monitoring/ui_exports.js index 49f167b0f1b10..e0c04411ef46b 100644 --- a/x-pack/legacy/plugins/monitoring/ui_exports.js +++ b/x-pack/legacy/plugins/monitoring/ui_exports.js @@ -45,7 +45,7 @@ export const getUiExports = () => { icon: 'plugins/monitoring/icons/monitoring.svg', euiIconType: 'monitoringApp', linkToLastSubUrl: false, - main: 'plugins/monitoring/monitoring', + main: 'plugins/monitoring/legacy', category: DEFAULT_APP_CATEGORIES.management, }, injectDefaultVars(server) { diff --git a/x-pack/legacy/plugins/remote_clusters/index.ts b/x-pack/legacy/plugins/remote_clusters/index.ts index ed992e3bf1921..5dd823e09eb8b 100644 --- a/x-pack/legacy/plugins/remote_clusters/index.ts +++ b/x-pack/legacy/plugins/remote_clusters/index.ts @@ -7,8 +7,6 @@ import { Legacy } from 'kibana'; import { resolve } from 'path'; import { PLUGIN } from './common'; -import { Plugin as RemoteClustersPlugin } from './plugin'; -import { createShim } from './shim'; export function remoteClusters(kibana: any) { return new kibana.Plugin({ @@ -43,25 +41,6 @@ export function remoteClusters(kibana: any) { config.get('xpack.remote_clusters.enabled') && config.get('xpack.index_management.enabled') ); }, - init(server: Legacy.Server) { - const { - coreSetup, - pluginsSetup: { - license: { registerLicenseChecker }, - }, - } = createShim(server, PLUGIN.ID); - - const remoteClustersPlugin = new RemoteClustersPlugin(); - - // Set up plugin. - remoteClustersPlugin.setup(coreSetup); - - registerLicenseChecker( - server, - PLUGIN.ID, - PLUGIN.getI18nName(), - PLUGIN.MINIMUM_LICENSE_REQUIRED - ); - }, + init(server: any) {}, }); } diff --git a/x-pack/legacy/plugins/remote_clusters/plugin.ts b/x-pack/legacy/plugins/remote_clusters/plugin.ts deleted file mode 100644 index a15ad553c9188..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/plugin.ts +++ /dev/null @@ -1,30 +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 { API_BASE_PATH } from './common'; -import { CoreSetup } from './shim'; -import { - registerGetRoute, - registerAddRoute, - registerUpdateRoute, - registerDeleteRoute, -} from './server/routes/api'; - -export class Plugin { - public setup(core: CoreSetup): void { - const { - http: { createRouter, isEsError }, - } = core; - - const router = createRouter(API_BASE_PATH); - - // Register routes. - registerGetRoute(router); - registerAddRoute(router); - registerUpdateRoute(router); - registerDeleteRoute(router, isEsError); - } -} diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_edit/remote_cluster_edit.js index 42b9eabc8e33e..f48d854da7255 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_edit/remote_cluster_edit.js @@ -37,7 +37,7 @@ export class RemoteClusterEdit extends Component { stopEditingCluster: PropTypes.func, editCluster: PropTypes.func, isEditingCluster: PropTypes.bool, - getEditClusterError: PropTypes.string, + getEditClusterError: PropTypes.object, clearEditClusterErrors: PropTypes.func, openDetailPanel: PropTypes.func, }; diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js b/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js index 47eb192714d7a..4086a91e29021 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js @@ -63,9 +63,7 @@ export const removeClusters = names => async (dispatch, getState) => { const { name, error: { - output: { - payload: { message }, - }, + payload: { message }, }, } = errors[0]; diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.test.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.test.ts deleted file mode 100644 index 0ed2f85fa904f..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.test.ts +++ /dev/null @@ -1,112 +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 Boom from 'boom'; -import { Request, ResponseToolkit } from 'hapi'; -import { wrapCustomError } from '../../../../../server/lib/create_router'; -import { addHandler } from './add_route'; - -describe('[API Routes] Remote Clusters addHandler()', () => { - const mockResponseToolkit = {} as ResponseToolkit; - - it('returns success', async () => { - const mockCreateRequest = ({ - payload: { - name: 'test_cluster', - seeds: [], - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce(null) - .mockReturnValueOnce({ - acknowledged: true, - persistent: { - cluster: { - remote: { - test_cluster: { - cluster: true, - }, - }, - }, - }, - }); - - const response = await addHandler(mockCreateRequest, callWithRequest, mockResponseToolkit); - const expectedResponse = { - acknowledged: true, - }; - expect(response).toEqual(expectedResponse); - }); - - it('throws an error if the response does not contain cluster information', async () => { - const mockCreateRequest = ({ - payload: { - name: 'test_cluster', - seeds: [], - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce(null) - .mockReturnValueOnce({ - acknowledged: true, - persistent: {}, - }); - - const expectedError = wrapCustomError( - new Error('Unable to add cluster, no response returned from ES.'), - 400 - ); - - await expect( - addHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(expectedError); - }); - - it('throws an error if the cluster already exists', async () => { - const mockCreateRequest = ({ - payload: { - name: 'test_cluster', - seeds: [], - }, - } as unknown) as Request; - - const callWithRequest = jest.fn().mockReturnValueOnce({ test_cluster: true }); - - const expectedError = wrapCustomError( - new Error('There is already a remote cluster with that name.'), - 409 - ); - - await expect( - addHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(expectedError); - }); - - it('throws an ES error when one is received', async () => { - const mockCreateRequest = ({ - payload: { - name: 'test_cluster', - seeds: [], - }, - } as unknown) as Request; - - const mockError = new Error() as any; - mockError.response = JSON.stringify({ error: 'Test error' }); - - const callWithRequest = jest - .fn() - .mockReturnValueOnce(null) - .mockRejectedValueOnce(mockError); - - await expect( - addHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(Boom.boomify(mockError)); - }); -}); diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.ts deleted file mode 100644 index 36b8d4fe7c3a0..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/add_route.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; - -import { - Router, - RouterRouteHandler, - wrapCustomError, -} from '../../../../../server/lib/create_router'; -import { serializeCluster } from '../../../common/cluster_serialization'; -import { doesClusterExist } from '../../lib/does_cluster_exist'; - -export const register = (router: Router): void => { - router.post('', addHandler); -}; - -export const addHandler: RouterRouteHandler = async (req, callWithRequest): Promise => { - const { name, seeds, skipUnavailable } = req.payload as any; - - // Check if cluster already exists. - const existingCluster = await doesClusterExist(callWithRequest, name); - if (existingCluster) { - const conflictError = wrapCustomError( - new Error('There is already a remote cluster with that name.'), - 409 - ); - - throw conflictError; - } - - const addClusterPayload = serializeCluster({ name, seeds, skipUnavailable }); - const response = await callWithRequest('cluster.putSettings', { body: addClusterPayload }); - const acknowledged = get(response, 'acknowledged'); - const cluster = get(response, `persistent.cluster.remote.${name}`); - - if (acknowledged && cluster) { - return { - acknowledged: true, - }; - } - - // If for some reason the ES response did not acknowledge, - // return an error. This shouldn't happen. - throw wrapCustomError(new Error('Unable to add cluster, no response returned from ES.'), 400); -}; diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.test.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.test.ts deleted file mode 100644 index b7eeffcb75105..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.test.ts +++ /dev/null @@ -1,158 +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 Boom from 'boom'; -import { Request, ResponseToolkit } from 'hapi'; -import { wrapCustomError } from '../../../../../server/lib/create_router'; -import { createDeleteHandler } from './delete_route'; - -describe('[API Routes] Remote Clusters deleteHandler()', () => { - const mockResponseToolkit = {} as ResponseToolkit; - - const isEsError = () => true; - const deleteHandler = createDeleteHandler(isEsError); - - it('returns names of deleted remote cluster', async () => { - const mockCreateRequest = ({ - params: { - nameOrNames: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockReturnValueOnce({ - acknowledged: true, - persistent: { - cluster: { - remote: {}, - }, - }, - }); - - const response = await deleteHandler(mockCreateRequest, callWithRequest, mockResponseToolkit); - const expectedResponse = { errors: [], itemsDeleted: ['test_cluster'] }; - expect(response).toEqual(expectedResponse); - }); - - it('returns names of multiple deleted remote clusters', async () => { - const mockCreateRequest = ({ - params: { - nameOrNames: 'test_cluster1,test_cluster2', - }, - } as unknown) as Request; - - const clusterExistsEsResponseMock = { test_cluster1: true, test_cluster2: true }; - - const successfulDeletionEsResponseMock = { - acknowledged: true, - persistent: { - cluster: { - remote: {}, - }, - }, - }; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce(clusterExistsEsResponseMock) - .mockReturnValueOnce(clusterExistsEsResponseMock) - .mockReturnValueOnce(successfulDeletionEsResponseMock) - .mockReturnValueOnce(successfulDeletionEsResponseMock); - - const response = await deleteHandler(mockCreateRequest, callWithRequest, mockResponseToolkit); - const expectedResponse = { errors: [], itemsDeleted: ['test_cluster1', 'test_cluster2'] }; - expect(response).toEqual(expectedResponse); - }); - - it('returns an error if the response contains cluster information', async () => { - const mockCreateRequest = ({ - params: { - nameOrNames: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockReturnValueOnce({ - acknowledged: true, - persistent: { - cluster: { - remote: { - test_cluster: {}, - }, - }, - }, - }); - - const response = await deleteHandler(mockCreateRequest, callWithRequest); - const expectedResponse = { - errors: [ - { - name: 'test_cluster', - error: wrapCustomError( - new Error('Unable to delete cluster, information still returned from ES.'), - 400 - ), - }, - ], - itemsDeleted: [], - }; - expect(response).toEqual(expectedResponse); - }); - - it(`returns an error if the cluster doesn't exist`, async () => { - const mockCreateRequest = ({ - params: { - nameOrNames: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest.fn().mockReturnValueOnce({}); - - const response = await deleteHandler(mockCreateRequest, callWithRequest); - const expectedResponse = { - errors: [ - { - name: 'test_cluster', - error: wrapCustomError(new Error('There is no remote cluster with that name.'), 404), - }, - ], - itemsDeleted: [], - }; - expect(response).toEqual(expectedResponse); - }); - - it('forwards an ES error when one is received', async () => { - const mockCreateRequest = ({ - params: { - nameOrNames: 'test_cluster', - }, - } as unknown) as Request; - - const mockError = new Error() as any; - mockError.response = JSON.stringify({ error: 'Test error' }); - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockRejectedValueOnce(mockError); - - const response = await deleteHandler(mockCreateRequest, callWithRequest); - const expectedResponse = { - errors: [ - { - name: 'test_cluster', - error: Boom.boomify(mockError), - }, - ], - itemsDeleted: [], - }; - expect(response).toEqual(expectedResponse); - }); -}); diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.ts deleted file mode 100644 index eff7c66b265b8..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/delete_route.ts +++ /dev/null @@ -1,102 +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 { get } from 'lodash'; - -import { - Router, - RouterRouteHandler, - wrapCustomError, - wrapEsError, - wrapUnknownError, -} from '../../../../../server/lib/create_router'; -import { serializeCluster } from '../../../common/cluster_serialization'; -import { doesClusterExist } from '../../lib/does_cluster_exist'; - -export const register = (router: Router, isEsError: any): void => { - router.delete('/{nameOrNames}', createDeleteHandler(isEsError)); -}; - -export const createDeleteHandler: any = (isEsError: any) => { - const deleteHandler: RouterRouteHandler = async ( - req, - callWithRequest - ): Promise<{ - itemsDeleted: any[]; - errors: any[]; - }> => { - const { nameOrNames } = req.params as any; - const names = nameOrNames.split(','); - - const itemsDeleted: any[] = []; - const errors: any[] = []; - - // Validator that returns an error if the remote cluster does not exist. - const validateClusterDoesExist = async (name: string) => { - try { - const existingCluster = await doesClusterExist(callWithRequest, name); - if (!existingCluster) { - return wrapCustomError(new Error('There is no remote cluster with that name.'), 404); - } - } catch (error) { - return wrapCustomError(error, 400); - } - }; - - // Send the request to delete the cluster and return an error if it could not be deleted. - const sendRequestToDeleteCluster = async (name: string) => { - try { - const body = serializeCluster({ name }); - const response = await callWithRequest('cluster.putSettings', { body }); - const acknowledged = get(response, 'acknowledged'); - const cluster = get(response, `persistent.cluster.remote.${name}`); - - if (acknowledged && !cluster) { - return null; - } - - // If for some reason the ES response still returns the cluster information, - // return an error. This shouldn't happen. - return wrapCustomError( - new Error('Unable to delete cluster, information still returned from ES.'), - 400 - ); - } catch (error) { - if (isEsError(error)) { - return wrapEsError(error); - } - - return wrapUnknownError(error); - } - }; - - const deleteCluster = async (clusterName: string) => { - // Validate that the cluster exists. - let error: any = await validateClusterDoesExist(clusterName); - - if (!error) { - // Delete the cluster. - error = await sendRequestToDeleteCluster(clusterName); - } - - if (error) { - errors.push({ name: clusterName, error }); - } else { - itemsDeleted.push(clusterName); - } - }; - - // Delete all our cluster in parallel. - await Promise.all(names.map(deleteCluster)); - - return { - itemsDeleted, - errors, - }; - }; - - return deleteHandler; -}; diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.test.ts deleted file mode 100644 index 4599e1b1e52e1..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.test.ts +++ /dev/null @@ -1,40 +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 { Request, ResponseToolkit } from 'hapi'; -import { getAllHandler } from './get_route'; - -describe('[API Routes] Remote Clusters getAllHandler()', () => { - const mockResponseToolkit = {} as ResponseToolkit; - - it('converts the ES response object to an array', async () => { - const callWithRequest = jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({ - abc: { seeds: ['xyz'] }, - foo: { seeds: ['bar'] }, - }); - - const response = await getAllHandler({} as Request, callWithRequest, mockResponseToolkit); - const expectedResponse: any[] = [ - { name: 'abc', seeds: ['xyz'], isConfiguredByNode: true }, - { name: 'foo', seeds: ['bar'], isConfiguredByNode: true }, - ]; - expect(response).toEqual(expectedResponse); - }); - - it('returns an empty array when ES responds with an empty object', async () => { - const callWithRequest = jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({}); - - const response = await getAllHandler({} as Request, callWithRequest, mockResponseToolkit); - const expectedResponse: any[] = []; - expect(response).toEqual(expectedResponse); - }); -}); diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.ts deleted file mode 100644 index 97bb59de85b89..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/get_route.ts +++ /dev/null @@ -1,40 +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 { get } from 'lodash'; - -import { Router, RouterRouteHandler } from '../../../../../server/lib/create_router'; -import { deserializeCluster } from '../../../common/cluster_serialization'; - -export const register = (router: Router): void => { - router.get('', getAllHandler); -}; - -// GET '/api/remote_clusters' -export const getAllHandler: RouterRouteHandler = async (req, callWithRequest): Promise => { - const clusterSettings = await callWithRequest('cluster.getSettings'); - const transientClusterNames = Object.keys(get(clusterSettings, `transient.cluster.remote`) || {}); - const persistentClusterNames = Object.keys( - get(clusterSettings, `persistent.cluster.remote`) || {} - ); - - const clustersByName = await callWithRequest('cluster.remoteInfo'); - const clusterNames = (clustersByName && Object.keys(clustersByName)) || []; - - return clusterNames.map((clusterName: string): any => { - const cluster = clustersByName[clusterName]; - const isTransient = transientClusterNames.includes(clusterName); - const isPersistent = persistentClusterNames.includes(clusterName); - // If the cluster hasn't been stored in the cluster state, then it's defined by the - // node's config file. - const isConfiguredByNode = !isTransient && !isPersistent; - - return { - ...deserializeCluster(clusterName, cluster), - isConfiguredByNode, - }; - }); -}; diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.test.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.test.ts deleted file mode 100644 index 4de92aef78357..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.test.ts +++ /dev/null @@ -1,120 +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 { Request, ResponseToolkit } from 'hapi'; -import { wrapCustomError } from '../../../../../server/lib/create_router'; -import { updateHandler } from './update_route'; - -describe('[API Routes] Remote Clusters updateHandler()', () => { - const mockResponseToolkit = {} as ResponseToolkit; - - it('returns the cluster information from Elasticsearch', async () => { - const mockCreateRequest = ({ - payload: { - seeds: [], - }, - params: { - name: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockReturnValueOnce(null) - .mockReturnValueOnce({ - acknowledged: true, - persistent: { - cluster: { - remote: { - test_cluster: { - seeds: [], - }, - }, - }, - }, - }); - - const response = await updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit); - const expectedResponse = { - name: 'test_cluster', - seeds: [], - isConfiguredByNode: false, - }; - expect(response).toEqual(expectedResponse); - }); - - it(`throws an error if the response doesn't contain cluster information`, async () => { - const mockCreateRequest = ({ - payload: { - seeds: [], - }, - params: { - name: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockReturnValueOnce({ - acknowledged: true, - persistent: {}, - }); - - const expectedError = wrapCustomError( - new Error('Unable to update cluster, no response returned from ES.'), - 400 - ); - await expect( - updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(expectedError); - }); - - it('throws an error if the cluster does not exist', async () => { - const mockCreateRequest = ({ - payload: { - seeds: [], - }, - params: { - name: 'test_cluster', - }, - } as unknown) as Request; - - const callWithRequest = jest.fn().mockReturnValueOnce({}); - - const expectedError = wrapCustomError( - new Error('There is no remote cluster with that name.'), - 404 - ); - await expect( - updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(expectedError); - }); - - it('throws an ES error when one is received', async () => { - const mockCreateRequest = ({ - payload: { - seeds: [], - }, - params: { - name: 'test_cluster', - }, - } as unknown) as Request; - - const mockError = new Error() as any; - mockError.response = JSON.stringify({ error: 'Test error' }); - - const callWithRequest = jest - .fn() - .mockReturnValueOnce({ test_cluster: true }) - .mockRejectedValueOnce(mockError); - - await expect( - updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) - ).rejects.toThrow(mockError); - }); -}); diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.ts deleted file mode 100644 index d6eedf7924ca3..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/server/routes/api/update_route.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; - -import { - Router, - RouterRouteHandler, - wrapCustomError, -} from '../../../../../server/lib/create_router'; -import { serializeCluster, deserializeCluster } from '../../../common/cluster_serialization'; -import { doesClusterExist } from '../../lib/does_cluster_exist'; - -export const register = (router: Router): void => { - router.put('/{name}', updateHandler); -}; - -export const updateHandler: RouterRouteHandler = async (req, callWithRequest): Promise => { - const { name } = req.params as any; - const { seeds, skipUnavailable } = req.payload as any; - - // Check if cluster does exist. - const existingCluster = await doesClusterExist(callWithRequest, name); - if (!existingCluster) { - throw wrapCustomError(new Error('There is no remote cluster with that name.'), 404); - } - - // Delete existing cluster settings. - // This is a workaround for: https://github.com/elastic/elasticsearch/issues/37799 - const deleteClusterPayload = serializeCluster({ name }); - await callWithRequest('cluster.putSettings', { body: deleteClusterPayload }); - - // Update cluster as new settings - const updateClusterPayload = serializeCluster({ name, seeds, skipUnavailable }); - const response = await callWithRequest('cluster.putSettings', { body: updateClusterPayload }); - const acknowledged = get(response, 'acknowledged'); - const cluster = get(response, `persistent.cluster.remote.${name}`); - - if (acknowledged && cluster) { - return { - ...deserializeCluster(name, cluster), - isConfiguredByNode: false, - }; - } - - // If for some reason the ES response did not acknowledge, - // return an error. This shouldn't happen. - throw wrapCustomError(new Error('Unable to update cluster, no response returned from ES.'), 400); -}; diff --git a/x-pack/legacy/plugins/remote_clusters/shim.ts b/x-pack/legacy/plugins/remote_clusters/shim.ts deleted file mode 100644 index d81f685992156..0000000000000 --- a/x-pack/legacy/plugins/remote_clusters/shim.ts +++ /dev/null @@ -1,41 +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 { Legacy } from 'kibana'; -import { createRouter, isEsErrorFactory, Router } from '../../server/lib/create_router'; -import { registerLicenseChecker } from '../../server/lib/register_license_checker'; - -export interface CoreSetup { - http: { - createRouter(basePath: string): Router; - isEsError(error: any): boolean; - }; -} - -export interface Plugins { - license: { - registerLicenseChecker: typeof registerLicenseChecker; - }; -} - -export function createShim( - server: Legacy.Server, - pluginId: string -): { coreSetup: CoreSetup; pluginsSetup: Plugins } { - return { - coreSetup: { - http: { - createRouter: (basePath: string) => createRouter(server, pluginId, basePath), - isEsError: isEsErrorFactory(server), - }, - }, - pluginsSetup: { - license: { - registerLicenseChecker, - }, - }, - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts index d1fa44773972f..9ab434e6a058b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts @@ -6,7 +6,10 @@ import expect from '@kbn/expect'; -import { fieldFormats } from '../../../../../../../../src/plugins/data/server'; +import { + fieldFormats, + FieldFormatsGetConfigFn, +} from '../../../../../../../../src/plugins/data/server'; import { fieldFormatMapFactory } from './field_format_map'; type ConfigValue = { number: { id: string; params: {} } } | string; @@ -29,7 +32,7 @@ describe('field format map', function() { number: { id: 'number', params: {} }, }; configMock['format:number:defaultPattern'] = '0,0.[000]'; - const getConfig = ((key: string) => configMock[key]) as fieldFormats.GetConfigFn; + const getConfig = ((key: string) => configMock[key]) as FieldFormatsGetConfigFn; const testValue = '4000'; const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts index dba97b508f93e..e1459e195d9f6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts @@ -5,7 +5,10 @@ */ import _ from 'lodash'; -import { fieldFormats } from '../../../../../../../../src/plugins/data/server'; +import { + FieldFormatConfig, + IFieldFormatsRegistry, +} from '../../../../../../../../src/plugins/data/server'; interface IndexPatternSavedObject { attributes: { @@ -25,7 +28,7 @@ interface IndexPatternSavedObject { */ export function fieldFormatMapFactory( indexPatternSavedObject: IndexPatternSavedObject, - fieldFormatsRegistry: fieldFormats.FieldFormatsRegistry + fieldFormatsRegistry: IFieldFormatsRegistry ) { const formatsMap = new Map(); @@ -33,7 +36,7 @@ export function fieldFormatMapFactory( if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); Object.keys(fieldFormatMap).forEach(fieldName => { - const formatConfig: fieldFormats.IFieldFormatConfig = fieldFormatMap[fieldName]; + const formatConfig: FieldFormatConfig = fieldFormatMap[fieldName]; if (!_.isEmpty(formatConfig)) { formatsMap.set( diff --git a/x-pack/legacy/plugins/reporting/public/register_feature.js b/x-pack/legacy/plugins/reporting/public/register_feature.js deleted file mode 100644 index 98de06fa16e33..0000000000000 --- a/x-pack/legacy/plugins/reporting/public/register_feature.js +++ /dev/null @@ -1,28 +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 { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'reporting', - title: i18n.translate('xpack.reporting.registerFeature.reportingTitle', { - defaultMessage: 'Reporting', - }), - description: i18n.translate('xpack.reporting.registerFeature.reportingDescription', { - defaultMessage: 'Manage your reports generated from Discover, Visualize, and Dashboard.', - }), - icon: 'reportingApp', - path: '/app/kibana#/management/kibana/reporting', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/reporting/public/register_feature.ts b/x-pack/legacy/plugins/reporting/public/register_feature.ts new file mode 100644 index 0000000000000..4e8d32facfcec --- /dev/null +++ b/x-pack/legacy/plugins/reporting/public/register_feature.ts @@ -0,0 +1,27 @@ +/* + * 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 { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; + +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register({ + id: 'reporting', + title: i18n.translate('xpack.reporting.registerFeature.reportingTitle', { + defaultMessage: 'Reporting', + }), + description: i18n.translate('xpack.reporting.registerFeature.reportingDescription', { + defaultMessage: 'Manage your reports generated from Discover, Visualize, and Dashboard.', + }), + icon: 'reportingApp', + path: '/app/kibana#/management/kibana/reporting', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, +}); diff --git a/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx b/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx index 8e0da6a69225e..4153c7cdbdb0b 100644 --- a/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx +++ b/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx @@ -8,16 +8,14 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; // @ts-ignore: implicit any for JS file import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { npSetup } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; import React from 'react'; -import chrome from 'ui/chrome'; import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content'; import { ShareContext } from '../../../../../../src/plugins/share/public'; const { core } = npSetup; async function reportingProvider() { - const injector = await chrome.dangerouslyGetActiveInjector(); const getShareMenuItems = ({ objectType, objectId, @@ -31,7 +29,10 @@ async function reportingProvider() { } // Dashboard only mode does not currently support reporting // https://github.com/elastic/kibana/issues/18286 - if (objectType === 'dashboard' && injector.get('dashboardConfig').getHideWriteControls()) { + if ( + objectType === 'dashboard' && + npStart.plugins.kibanaLegacy.dashboardConfig.getHideWriteControls() + ) { return []; } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 9016398463b5f..fd89c40f010b7 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -86,6 +86,7 @@ export const security = kibana => tenant: server.newPlatform.setup.core.http.basePath.serverBasePath, }, enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled'), + logoutUrl: `${server.newPlatform.setup.core.http.basePath.serverBasePath}/logout`, }; }, }, diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts index be66fdc86be36..64002aadc86d8 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts @@ -4,40 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { OVERVIEW_PAGE } from '../../lib/urls'; -import { clearFetch, stubApi } from '../../lib/fixtures/helpers'; -import { - HOST_STATS, - NETWORK_STATS, - OVERVIEW_HOST_STATS, - OVERVIEW_NETWORK_STATS, - STAT_AUDITD, -} from '../../lib/overview/selectors'; +import { OVERVIEW_PAGE } from '../../../urls/navigation'; +import { HOST_STATS, NETWORK_STATS } from '../../../screens/overview'; +import { expandHostStats, expandNetworkStats } from '../../../tasks/overview'; import { loginAndWaitForPage } from '../../lib/util/helpers'; describe('Overview Page', () => { - beforeEach(() => { - clearFetch(); - stubApi('overview'); + before(() => { + cy.stubSIEMapi('overview'); loginAndWaitForPage(OVERVIEW_PAGE); }); - it('Host and Network stats render with correct values', () => { - cy.get(OVERVIEW_HOST_STATS) - .find('button') - .invoke('click'); - - cy.get(OVERVIEW_NETWORK_STATS) - .find('button') - .invoke('click'); - - cy.get(STAT_AUDITD.domId); + it('Host stats render with correct values', () => { + expandHostStats(); HOST_STATS.forEach(stat => { cy.get(stat.domId) .invoke('text') .should('eq', stat.value); }); + }); + + it('Network stats render with correct values', () => { + expandNetworkStats(); NETWORK_STATS.forEach(stat => { cy.get(stat.domId) diff --git a/x-pack/legacy/plugins/siem/cypress/screens/overview.ts b/x-pack/legacy/plugins/siem/cypress/screens/overview.ts new file mode 100644 index 0000000000000..95facc8974400 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/screens/overview.ts @@ -0,0 +1,144 @@ +/* + * 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. + */ + +// Host Stats +export const STAT_AUDITD = { + value: '123', + domId: '[data-test-subj="host-stat-auditbeatAuditd"]', +}; +export const ENDGAME_DNS = { + value: '391', + domId: '[data-test-subj="host-stat-endgameDns"]', +}; +export const ENDGAME_FILE = { + value: '392', + domId: '[data-test-subj="host-stat-endgameFile"]', +}; +export const ENDGAME_IMAGE_LOAD = { + value: '393', + domId: '[data-test-subj="host-stat-endgameImageLoad"]', +}; +export const ENDGAME_NETWORK = { + value: '394', + domId: '[data-test-subj="host-stat-endgameNetwork"]', +}; +export const ENDGAME_PROCESS = { + value: '395', + domId: '[data-test-subj="host-stat-endgameProcess"]', +}; +export const ENDGAME_REGISTRY = { + value: '396', + domId: '[data-test-subj="host-stat-endgameRegistry"]', +}; +export const ENDGAME_SECURITY = { + value: '397', + domId: '[data-test-subj="host-stat-endgameSecurity"]', +}; +export const STAT_FILEBEAT = { + value: '890', + domId: '[data-test-subj="host-stat-filebeatSystemModule"]', +}; +export const STAT_FIM = { + value: '345', + domId: '[data-test-subj="host-stat-auditbeatFIM"]', +}; +export const STAT_LOGIN = { + value: '456', + domId: '[data-test-subj="host-stat-auditbeatLogin"]', +}; +export const STAT_PACKAGE = { + value: '567', + domId: '[data-test-subj="host-stat-auditbeatPackage"]', +}; +export const STAT_PROCESS = { + value: '678', + domId: '[data-test-subj="host-stat-auditbeatProcess"]', +}; +export const STAT_USER = { + value: '789', + domId: '[data-test-subj="host-stat-auditbeatUser"]', +}; +export const STAT_WINLOGBEAT_SECURITY = { + value: '70', + domId: '[data-test-subj="host-stat-winlogbeatSecurity"]', +}; +export const STAT_WINLOGBEAT_MWSYSMON_OPERATIONAL = { + value: '30', + domId: '[data-test-subj="host-stat-winlogbeatMWSysmonOperational"]', +}; + +export const HOST_STATS = [ + STAT_AUDITD, + ENDGAME_DNS, + ENDGAME_FILE, + ENDGAME_IMAGE_LOAD, + ENDGAME_NETWORK, + ENDGAME_PROCESS, + ENDGAME_REGISTRY, + ENDGAME_SECURITY, + STAT_FILEBEAT, + STAT_FIM, + STAT_LOGIN, + STAT_PACKAGE, + STAT_PROCESS, + STAT_USER, + STAT_WINLOGBEAT_SECURITY, + STAT_WINLOGBEAT_MWSYSMON_OPERATIONAL, +]; + +// Network Stats +export const STAT_SOCKET = { + value: '578,502', + domId: '[data-test-subj="network-stat-auditbeatSocket"]', +}; +export const STAT_CISCO = { + value: '999', + domId: '[data-test-subj="network-stat-filebeatCisco"]', +}; +export const STAT_NETFLOW = { + value: '2,544', + domId: '[data-test-subj="network-stat-filebeatNetflow"]', +}; +export const STAT_PANW = { + value: '678', + domId: '[data-test-subj="network-stat-filebeatPanw"]', +}; +export const STAT_SURICATA = { + value: '303,699', + domId: '[data-test-subj="network-stat-filebeatSuricata"]', +}; +export const STAT_ZEEK = { + value: '71,129', + domId: '[data-test-subj="network-stat-filebeatZeek"]', +}; +export const STAT_DNS = { + value: '1,090', + domId: '[data-test-subj="network-stat-packetbeatDNS"]', +}; +export const STAT_FLOW = { + value: '722,153', + domId: '[data-test-subj="network-stat-packetbeatFlow"]', +}; +export const STAT_TLS = { + value: '340', + domId: '[data-test-subj="network-stat-packetbeatTLS"]', +}; + +export const NETWORK_STATS = [ + STAT_SOCKET, + STAT_CISCO, + STAT_NETFLOW, + STAT_PANW, + STAT_SURICATA, + STAT_ZEEK, + STAT_DNS, + STAT_FLOW, + STAT_TLS, +]; + +export const OVERVIEW_HOST_STATS = '[data-test-subj="overview-hosts-stats"]'; + +export const OVERVIEW_NETWORK_STATS = '[data-test-subj="overview-network-stats"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/support/commands.js b/x-pack/legacy/plugins/siem/cypress/support/commands.js index 9a2e54b102c5e..e697dbce0f249 100644 --- a/x-pack/legacy/plugins/siem/cypress/support/commands.js +++ b/x-pack/legacy/plugins/siem/cypress/support/commands.js @@ -29,3 +29,13 @@ // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) + +Cypress.Commands.add('stubSIEMapi', function(dataFileName) { + cy.on('window:before:load', win => { + // @ts-ignore no null, this is a temp hack see issue above + win.fetch = null; + }); + cy.server(); + cy.fixture(dataFileName).as(`${dataFileName}JSON`); + cy.route('POST', 'api/siem/graphql', `@${dataFileName}JSON`); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/support/index.d.ts b/x-pack/legacy/plugins/siem/cypress/support/index.d.ts new file mode 100644 index 0000000000000..5d5173170a9f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/support/index.d.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +declare namespace Cypress { + interface Chainable { + stubSIEMapi(dataFileName: string): Chainable; + } +} diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/overview.ts b/x-pack/legacy/plugins/siem/cypress/tasks/overview.ts new file mode 100644 index 0000000000000..0ca4059a90097 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/tasks/overview.ts @@ -0,0 +1,21 @@ +/* + * 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 { OVERVIEW_HOST_STATS, OVERVIEW_NETWORK_STATS } from '../screens/overview'; + +export const expand = (statType: string) => { + cy.get(statType) + .find('button') + .invoke('click'); +}; + +export const expandHostStats = () => { + expand(OVERVIEW_HOST_STATS); +}; + +export const expandNetworkStats = () => { + expand(OVERVIEW_NETWORK_STATS); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/urls/navigation.ts b/x-pack/legacy/plugins/siem/cypress/urls/navigation.ts index 4675829df839a..35db3003ac436 100644 --- a/x-pack/legacy/plugins/siem/cypress/urls/navigation.ts +++ b/x-pack/legacy/plugins/siem/cypress/urls/navigation.ts @@ -5,3 +5,4 @@ */ export const TIMELINES_PAGE = '/app/siem#/timelines'; +export const OVERVIEW_PAGE = '/app/siem#/overview'; diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx index 85e2b3b3fe384..8f261da629f94 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx @@ -8,15 +8,18 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ThemeProvider } from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import { autocomplete } from '../../../../../../../../src/plugins/data/public'; +import { + QuerySuggestion, + QuerySuggestionTypes, +} from '../../../../../../../../src/plugins/data/public'; import { SuggestionItem } from '../suggestion_item'; -const suggestion: autocomplete.QuerySuggestion = { +const suggestion: QuerySuggestion = { description: 'Description...', end: 3, start: 1, text: 'Text...', - type: autocomplete.QuerySuggestionsTypes.Value, + type: QuerySuggestionTypes.Value, }; storiesOf('components/SuggestionItem', module).add('example', () => ( diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx index 552aaa5889719..55e114818ffea 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx @@ -10,15 +10,18 @@ import { mount, shallow } from 'enzyme'; import { noop } from 'lodash/fp'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { + QuerySuggestion, + QuerySuggestionTypes, +} from '../../../../../../../src/plugins/data/public'; import { TestProviders } from '../../mock'; import { AutocompleteField } from '.'; -const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ +const mockAutoCompleteData: QuerySuggestion[] = [ { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.ephemeral_id ', description: '

Filter results that contain agent.ephemeral_id

', @@ -26,7 +29,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.hostname ', description: '

Filter results that contain agent.hostname

', @@ -34,7 +37,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.id ', description: '

Filter results that contain agent.id

', @@ -42,7 +45,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.name ', description: '

Filter results that contain agent.name

', @@ -50,7 +53,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.type ', description: '

Filter results that contain agent.type

', @@ -58,7 +61,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.version ', description: '

Filter results that contain agent.version

', @@ -66,7 +69,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.test1 ', description: '

Filter results that contain agent.test1

', @@ -74,7 +77,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.test2 ', description: '

Filter results that contain agent.test2

', @@ -82,7 +85,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.test3 ', description: '

Filter results that contain agent.test3

', @@ -90,7 +93,7 @@ const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ end: 1, }, { - type: autocomplete.QuerySuggestionsTypes.Field, + type: QuerySuggestionTypes.Field, text: 'agent.test4 ', description: '

Filter results that contain agent.test4

', diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx index 2f76ae21944be..f051e18f8acab 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx @@ -11,7 +11,7 @@ import { EuiPanel, } from '@elastic/eui'; import React from 'react'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx index 44bc65bb0dc15..f99a545d558f7 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { transparentize } from 'polished'; import React from 'react'; import styled from 'styled-components'; import euiStyled from '../../../../../common/eui_styled_components'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: autocomplete.QuerySuggestion; + suggestion: QuerySuggestion; } export const SuggestionItem = React.memo( diff --git a/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx b/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx index e74299f57c934..a219dca595cda 100644 --- a/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx @@ -24,10 +24,24 @@ export const HelpMenu = React.memo(() => { href: docLinks.links.siem.guide, iconType: 'documents', linkType: 'custom', + target: '_blank', + rel: 'noopener', + }, + { + content: i18n.translate('xpack.siem.chrome.helpMenu.documentation.ecs', { + defaultMessage: 'ECS documentation', + }), + href: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/ecs/current/index.html`, + iconType: 'documents', + linkType: 'custom', + target: '_blank', + rel: 'noopener', }, { linkType: 'discuss', href: 'https://discuss.elastic.co/c/siem', + target: '_blank', + rel: 'noopener', }, ], }); diff --git a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx index 4eb51dfe6407c..af4eb1ff7a5e1 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx @@ -5,7 +5,7 @@ */ import React, { useState } from 'react'; -import { autocomplete, IIndexPattern } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestion, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { useKibana } from '../../lib/kibana'; type RendererResult = React.ReactElement | null; @@ -15,7 +15,7 @@ interface KueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -30,7 +30,7 @@ export const KueryAutocompletion = React.memo const [currentRequest, setCurrentRequest] = useState( null ); - const [suggestions, setSuggestions] = useState([]); + const [suggestions, setSuggestions] = useState([]); const kibana = useKibana(); const loadSuggestions = async ( expression: string, diff --git a/x-pack/legacy/plugins/spaces/common/constants.ts b/x-pack/legacy/plugins/spaces/common/constants.ts deleted file mode 100644 index 11882ca2f1b3a..0000000000000 --- a/x-pack/legacy/plugins/spaces/common/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const DEFAULT_SPACE_ID = `default`; - -/** - * The minimum number of spaces required to show a search control. - */ -export const SPACE_SEARCH_COUNT_THRESHOLD = 8; - -/** - * The maximum number of characters allowed in the Space Avatar's initials - */ -export const MAX_SPACE_INITIALS = 2; - -/** - * The type name used within the Monitoring index to publish spaces stats. - * @type {string} - */ -export const KIBANA_SPACES_STATS_TYPE = 'spaces'; - -/** - * The path to enter a space. - */ -export const ENTER_SPACE_PATH = '/spaces/enter'; diff --git a/x-pack/legacy/plugins/spaces/common/is_reserved_space.test.ts b/x-pack/legacy/plugins/spaces/common/is_reserved_space.test.ts deleted file mode 100644 index dd1372183ed8a..0000000000000 --- a/x-pack/legacy/plugins/spaces/common/is_reserved_space.test.ts +++ /dev/null @@ -1,34 +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 { isReservedSpace } from './is_reserved_space'; -import { Space } from './model/space'; - -test('it returns true for reserved spaces', () => { - const space: Space = { - id: '', - name: '', - disabledFeatures: [], - _reserved: true, - }; - - expect(isReservedSpace(space)).toEqual(true); -}); - -test('it returns false for non-reserved spaces', () => { - const space: Space = { - id: '', - name: '', - disabledFeatures: [], - }; - - expect(isReservedSpace(space)).toEqual(false); -}); - -test('it handles empty input', () => { - // @ts-ignore - expect(isReservedSpace()).toEqual(false); -}); diff --git a/x-pack/legacy/plugins/spaces/common/is_reserved_space.ts b/x-pack/legacy/plugins/spaces/common/is_reserved_space.ts deleted file mode 100644 index 788ef80c194ce..0000000000000 --- a/x-pack/legacy/plugins/spaces/common/is_reserved_space.ts +++ /dev/null @@ -1,18 +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 { get } from 'lodash'; -import { Space } from './model/space'; - -/** - * Returns whether the given Space is reserved or not. - * - * @param space the space - * @returns boolean - */ -export function isReservedSpace(space?: Partial | null): boolean { - return get(space, '_reserved', false); -} diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 2e6b878794777..ab3388ae96475 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -17,7 +17,7 @@ import { wrapError } from './server/lib/errors'; import { migrateToKibana660 } from './server/lib/migrations'; // @ts-ignore import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; -import { initSpaceSelectorView, initEnterSpaceView } from './server/routes/views'; +import { initEnterSpaceView } from './server/routes/views'; export interface LegacySpacesPlugin { getSpaceId: (request: Legacy.Request) => ReturnType; @@ -50,15 +50,7 @@ export const spaces = (kibana: Record) => uiExports: { styleSheetPaths: resolve(__dirname, 'public/index.scss'), managementSections: [], - apps: [ - { - id: 'space_selector', - title: 'Spaces', - main: 'plugins/spaces/space_selector', - url: 'space_selector', - hidden: true, - }, - ], + apps: [], hacks: ['plugins/spaces/legacy'], mappings, migrations: { @@ -131,11 +123,9 @@ export const spaces = (kibana: Record) => create: (pluginId: string) => new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info), }, - xpackMain: server.plugins.xpack_main, }); initEnterSpaceView(server); - initSpaceSelectorView(server); watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => { await createDefaultSpace(); diff --git a/x-pack/legacy/plugins/spaces/public/__mocks__/xpack_info.ts b/x-pack/legacy/plugins/spaces/public/__mocks__/xpack_info.ts deleted file mode 100644 index e3467b88dbc61..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/__mocks__/xpack_info.ts +++ /dev/null @@ -1,18 +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. - */ - -jest.mock('../../../xpack_main/public/services/xpack_info', () => { - return { - xpackInfo: { - get: jest.fn().mockImplementation((key: string) => { - if (key === 'features.security.showLinks') { - return true; - } - throw new Error(`unexpected key: ${key}`); - }), - }, - }; -}); diff --git a/x-pack/legacy/plugins/spaces/public/index.scss b/x-pack/legacy/plugins/spaces/public/index.scss index 26269f1d31aa3..bb3481f96bc1e 100644 --- a/x-pack/legacy/plugins/spaces/public/index.scss +++ b/x-pack/legacy/plugins/spaces/public/index.scss @@ -1,16 +1,4 @@ // Import the EUI global scope so we can use EUI constants @import 'src/legacy/ui/public/styles/_styling_constants'; -/* Spaces plugin styles */ - -// Prefix all styles with "spc" to avoid conflicts. -// Examples -// spcChart -// spcChart__legend -// spcChart__legend--small -// spcChart__legend-isLoading - -@import './management/index'; -@import './nav_control/index'; -@import './space_selector/index'; -@import './copy_saved_objects_to_space/index'; +@import '../../../../plugins/spaces/public/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/legacy.ts b/x-pack/legacy/plugins/spaces/public/legacy.ts index 200cae5498595..c6740dae81717 100644 --- a/x-pack/legacy/plugins/spaces/public/legacy.ts +++ b/x-pack/legacy/plugins/spaces/public/legacy.ts @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectsManagementAction } from 'src/legacy/core_plugins/management/public'; +import { npSetup } from 'ui/new_platform'; +import routes from 'ui/routes'; +import { SpacesPluginSetup } from '../../../../plugins/spaces/public'; import { setup as managementSetup } from '../../../../../src/legacy/core_plugins/management/public/legacy'; -import { plugin } from '.'; -import { SpacesPlugin, PluginsSetup, PluginsStart } from './plugin'; -import './management/legacy_page_routes'; -const spacesPlugin: SpacesPlugin = plugin(); - -const pluginsSetup: PluginsSetup = { - home: npSetup.plugins.home, - management: managementSetup, - advancedSettings: npSetup.plugins.advancedSettings, +const legacyAPI = { + registerSavedObjectsManagementAction: (action: SavedObjectsManagementAction) => { + managementSetup.savedObjects.registry.register(action); + }, }; -const pluginsStart: PluginsStart = { - management: npStart.plugins.management, -}; +const spaces = (npSetup.plugins as any).spaces as SpacesPluginSetup; +if (spaces) { + spaces.registerLegacyAPI(legacyAPI); -export const setup = spacesPlugin.setup(npSetup.core, pluginsSetup); -export const start = spacesPlugin.start(npStart.core, pluginsStart); + routes.when('/management/spaces/list', { redirectTo: '/management/kibana/spaces' }); + routes.when('/management/spaces/create', { redirectTo: '/management/kibana/spaces/create' }); + routes.when('/management/spaces/edit/:spaceId', { + redirectTo: '/management/kibana/spaces/edit/:spaceId', + }); +} diff --git a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap deleted file mode 100644 index bce57527cd55a..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecureSpaceMessage doesn't render if security is not enabled 1`] = `""`; - -exports[`SecureSpaceMessage renders if security is enabled 1`] = ` - - - -

- - - , - } - } - /> -

-
-
-`; diff --git a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx deleted file mode 100644 index b43010fe5f326..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx +++ /dev/null @@ -1,34 +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 React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SecureSpaceMessage } from './secure_space_message'; - -let mockShowLinks: boolean = true; -jest.mock('../../../../../xpack_main/public/services/xpack_info', () => { - return { - xpackInfo: { - get: jest.fn().mockImplementation((key: string) => { - if (key === 'features.security.showLinks') { - return mockShowLinks; - } - throw new Error(`unexpected key: ${key}`); - }), - }, - }; -}); - -describe('SecureSpaceMessage', () => { - it(`doesn't render if security is not enabled`, () => { - mockShowLinks = false; - expect(shallowWithIntl()).toMatchSnapshot(); - }); - - it(`renders if security is enabled`, () => { - mockShowLinks = true; - expect(shallowWithIntl()).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx deleted file mode 100644 index 746b7e2ac4c98..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx +++ /dev/null @@ -1,47 +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 { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import React, { Fragment } from 'react'; -// @ts-ignore -import { xpackInfo } from '../../../../../xpack_main/public/services/xpack_info'; - -export const SecureSpaceMessage = ({}) => { - const showSecurityLinks = xpackInfo.get('features.security.showLinks'); - - if (showSecurityLinks) { - const rolesLinkTextAriaLabel = i18n.translate( - 'xpack.spaces.management.secureSpaceMessage.rolesLinkTextAriaLabel', - { defaultMessage: 'Roles management page' } - ); - return ( - - - -

- - - - ), - }} - /> -

-
-
- ); - } - return null; -}; diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap deleted file mode 100644 index 5879ff621d64a..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap +++ /dev/null @@ -1,324 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EnabledFeatures renders as expected 1`] = ` - - - - - - - - - - } -> - - - -

- -

-
- - -

- -

-

- - - , - } - } - /> -

-
-
- - - -
-
-`; diff --git a/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx b/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx deleted file mode 100644 index 8cf4a129e5b8f..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx +++ /dev/null @@ -1,149 +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. - */ -// @ts-ignore -import template from 'plugins/spaces/management/template.html'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import routes from 'ui/routes'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; -import { npStart } from 'ui/new_platform'; -import { ManageSpacePage } from './edit_space'; -import { SpacesGridPage } from './spaces_grid'; - -import { start as spacesNPStart } from '../legacy'; -import { Space } from '../../common/model/space'; - -const reactRootNodeId = 'manageSpacesReactRoot'; - -function getListBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: 'Spaces', - href: '#/management/spaces/list', - }, - ]; -} - -function getCreateBreadcrumbs() { - return [ - ...getListBreadcrumbs(), - { - text: 'Create', - }, - ]; -} - -function getEditBreadcrumbs(space?: Space) { - return [ - ...getListBreadcrumbs(), - { - text: space ? space.name : '...', - }, - ]; -} - -routes.when('/management/spaces/list', { - template, - k7Breadcrumbs: getListBreadcrumbs, - requireUICapability: 'management.kibana.spaces', - controller($scope: any) { - $scope.$$postDigest(() => { - const domNode = document.getElementById(reactRootNodeId); - - const { spacesManager } = spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); - -routes.when('/management/spaces/create', { - template, - k7Breadcrumbs: getCreateBreadcrumbs, - requireUICapability: 'management.kibana.spaces', - controller($scope: any) { - $scope.$$postDigest(() => { - const domNode = document.getElementById(reactRootNodeId); - - const { spacesManager } = spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); - -routes.when('/management/spaces/edit', { - redirectTo: '/management/spaces/list', -}); - -routes.when('/management/spaces/edit/:spaceId', { - template, - k7Breadcrumbs: () => getEditBreadcrumbs(), - requireUICapability: 'management.kibana.spaces', - controller($scope: any, $route: any) { - $scope.$$postDigest(async () => { - const domNode = document.getElementById(reactRootNodeId); - - const { spaceId } = $route.current.params; - - const { spacesManager } = await spacesNPStart; - - render( - - { - npStart.core.chrome.setBreadcrumbs(getEditBreadcrumbs(space)); - }} - capabilities={npStart.core.application.capabilities} - /> - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts deleted file mode 100644 index fbd39db6969bd..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts +++ /dev/null @@ -1,123 +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 { ManagementService } from '.'; - -const mockSections = { - getSection: jest.fn(), - getAllSections: jest.fn(), - navigateToApp: jest.fn(), -}; - -describe('ManagementService', () => { - describe('#start', () => { - it('registers the spaces management page under the kibana section', () => { - const mockKibanaSection = { - hasItem: jest.fn().mockReturnValue(false), - register: jest.fn(), - }; - - const managementStart = { - legacy: { - getSection: jest.fn().mockReturnValue(mockKibanaSection), - }, - sections: mockSections, - }; - - const deps = { - managementStart, - }; - - const service = new ManagementService(); - service.start(deps); - - expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); - expect(deps.managementStart.legacy.getSection).toHaveBeenCalledWith('kibana'); - - expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); - expect(mockKibanaSection.register).toHaveBeenCalledWith('spaces', { - name: 'spacesManagementLink', - order: 10, - display: 'Spaces', - url: `#/management/spaces/list`, - }); - }); - - it('will not register the spaces management page twice', () => { - const mockKibanaSection = { - hasItem: jest.fn().mockReturnValue(true), - register: jest.fn(), - }; - - const managementStart = { - legacy: { - getSection: jest.fn().mockReturnValue(mockKibanaSection), - }, - sections: mockSections, - }; - - const deps = { - managementStart, - }; - - const service = new ManagementService(); - service.start(deps); - - expect(mockKibanaSection.register).toHaveBeenCalledTimes(0); - }); - - it('will not register the spaces management page if the kibana section is missing', () => { - const managementStart = { - legacy: { - getSection: jest.fn().mockReturnValue(undefined), - }, - sections: mockSections, - }; - - const deps = { - managementStart, - }; - - const service = new ManagementService(); - service.start(deps); - - expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); - }); - }); - - describe('#stop', () => { - it('deregisters the spaces management page', () => { - const mockKibanaSection = { - hasItem: jest - .fn() - .mockReturnValueOnce(false) - .mockReturnValueOnce(true), - register: jest.fn(), - deregister: jest.fn(), - }; - - const managementStart = { - legacy: { - getSection: jest.fn().mockReturnValue(mockKibanaSection), - }, - sections: mockSections, - }; - - const deps = { - managementStart, - }; - - const service = new ManagementService(); - service.start(deps); - - service.stop(); - - expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); - expect(mockKibanaSection.deregister).toHaveBeenCalledTimes(1); - expect(mockKibanaSection.deregister).toHaveBeenCalledWith('spaces'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.ts deleted file mode 100644 index ada38f5cf3387..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/management_service.ts +++ /dev/null @@ -1,37 +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 { i18n } from '@kbn/i18n'; -import { ManagementStart } from 'src/plugins/management/public'; - -interface StartDeps { - managementStart: ManagementStart; -} - -const MANAGE_SPACES_KEY = 'spaces'; - -export class ManagementService { - private kibanaSection!: any; - - public start({ managementStart }: StartDeps) { - this.kibanaSection = managementStart.legacy.getSection('kibana'); - if (this.kibanaSection && !this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { - this.kibanaSection.register(MANAGE_SPACES_KEY, { - name: 'spacesManagementLink', - order: 10, - display: i18n.translate('xpack.spaces.displayName', { - defaultMessage: 'Spaces', - }), - url: `#/management/spaces/list`, - }); - } - } - - public stop() { - if (this.kibanaSection && this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { - this.kibanaSection.deregister(MANAGE_SPACES_KEY); - } - } -} diff --git a/x-pack/legacy/plugins/spaces/public/management/template.html b/x-pack/legacy/plugins/spaces/public/management/template.html deleted file mode 100644 index 3cd8e144b43fc..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/management/template.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap deleted file mode 100644 index 8e78f64ac59cb..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SpacesDescription renders without crashing 1`] = ` - - -

- Organize your dashboards and other saved objects into meaningful categories. -

-
-
- -
-
-`; diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx deleted file mode 100644 index 157dcab3e0be1..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx +++ /dev/null @@ -1,30 +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 { shallow } from 'enzyme'; -import React from 'react'; -import { SpacesDescription } from './spaces_description'; - -describe('SpacesDescription', () => { - it('renders without crashing', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/spaces/public/plugin.tsx b/x-pack/legacy/plugins/spaces/public/plugin.tsx deleted file mode 100644 index e6271ac3a0a70..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/plugin.tsx +++ /dev/null @@ -1,76 +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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { HomePublicPluginSetup } from 'src/plugins/home/public'; -import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; -import { ManagementStart } from 'src/plugins/management/public'; -import { AdvancedSettingsSetup } from 'src/plugins/advanced_settings/public'; -import { SpacesManager } from './spaces_manager'; -import { initSpacesNavControl } from './nav_control'; -import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; -import { CopySavedObjectsToSpaceService } from './copy_saved_objects_to_space'; -import { AdvancedSettingsService } from './advanced_settings'; -import { ManagementService } from './management'; - -export interface SpacesPluginStart { - spacesManager: SpacesManager | null; -} - -export interface PluginsSetup { - home?: HomePublicPluginSetup; - management: ManagementSetup; - advancedSettings: AdvancedSettingsSetup; -} - -export interface PluginsStart { - management: ManagementStart; -} - -export class SpacesPlugin implements Plugin { - private spacesManager!: SpacesManager; - - private managementService?: ManagementService; - - public setup(core: CoreSetup, plugins: PluginsSetup) { - const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; - this.spacesManager = new SpacesManager(serverBasePath, core.http); - - const copySavedObjectsToSpaceService = new CopySavedObjectsToSpaceService(); - copySavedObjectsToSpaceService.setup({ - spacesManager: this.spacesManager, - managementSetup: plugins.management, - }); - - const advancedSettingsService = new AdvancedSettingsService(); - advancedSettingsService.setup({ - getActiveSpace: () => this.spacesManager.getActiveSpace(), - componentRegistry: plugins.advancedSettings.component, - }); - - if (plugins.home) { - plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); - } - } - - public start(core: CoreStart, plugins: PluginsStart) { - initSpacesNavControl(this.spacesManager, core); - - this.managementService = new ManagementService(); - this.managementService.start({ managementStart: plugins.management }); - - return { - spacesManager: this.spacesManager, - }; - } - - public stop() { - if (this.managementService) { - this.managementService.stop(); - this.managementService = undefined; - } - } -} diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx deleted file mode 100644 index c1c1b6dc3a2f3..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx +++ /dev/null @@ -1,43 +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. - */ - -// @ts-ignore -import template from 'plugins/spaces/space_selector/space_selector.html'; -import chrome from 'ui/chrome'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { SpaceSelector } from './space_selector'; - -import { start as spacesNPStart } from '../legacy'; - -const module = uiModules.get('spaces_selector', []); -module.controller('spacesSelectorController', ($scope: any) => { - $scope.$$postDigest(() => { - const domNode = document.getElementById('spaceSelectorRoot'); - - const { spacesManager } = spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); -}); - -chrome.setVisible(false).setRootTemplate(template); diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html deleted file mode 100644 index 2dbf9fac3f68b..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts b/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts index e560d4278b407..337faa2a18fb6 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts @@ -5,7 +5,7 @@ */ import { Legacy } from 'kibana'; -import { ENTER_SPACE_PATH } from '../../../common/constants'; +import { ENTER_SPACE_PATH } from '../../../../../../plugins/spaces/common/constants'; import { wrapError } from '../../lib/errors'; export function initEnterSpaceView(server: Legacy.Server) { diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/index.ts b/x-pack/legacy/plugins/spaces/server/routes/views/index.ts index d7637e299652f..645e8bec48148 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/views/index.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/views/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { initSpaceSelectorView } from './space_selector'; export { initEnterSpaceView } from './enter_space'; diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts b/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts deleted file mode 100644 index 25c4179b99542..0000000000000 --- a/x-pack/legacy/plugins/spaces/server/routes/views/space_selector.ts +++ /dev/null @@ -1,19 +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 { Legacy } from 'kibana'; - -export function initSpaceSelectorView(server: Legacy.Server) { - const spaceSelector = server.getHiddenUiAppById('space_selector'); - - server.route({ - method: 'GET', - path: '/spaces/space_selector', - async handler(request, h) { - return (await h.renderAppWithDefaultConfig(spaceSelector)).takeover(); - }, - }); -} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx index 63c8885fe5864..b1eb3f38097b2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx @@ -14,7 +14,7 @@ import { useUrlParams } from '../../../hooks'; import { esKuery, IIndexPattern, - autocomplete, + QuerySuggestion, DataPublicPluginStart, } from '../../../../../../../../src/plugins/data/public'; @@ -23,7 +23,7 @@ const Container = styled.div` `; interface State { - suggestions: autocomplete.QuerySuggestion[]; + suggestions: QuerySuggestion[]; isLoadingIndexPattern: boolean; } diff --git a/x-pack/legacy/plugins/uptime/public/register_feature.ts b/x-pack/legacy/plugins/uptime/public/register_feature.ts index 885d4f6e1310f..2f83fa33ba4bc 100644 --- a/x-pack/legacy/plugins/uptime/public/register_feature.ts +++ b/x-pack/legacy/plugins/uptime/public/register_feature.ts @@ -5,12 +5,14 @@ */ import { i18n } from '@kbn/i18n'; -import { - FeatureCatalogueCategory, - FeatureCatalogueRegistryProvider, -} from 'ui/registry/feature_catalogue'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; -FeatureCatalogueRegistryProvider.register(() => ({ +const { + plugins: { home }, +} = npSetup; + +home.featureCatalogue.register({ id: 'uptime', title: i18n.translate('xpack.uptime.uptimeFeatureCatalogueTitle', { defaultMessage: 'Uptime' }), description: i18n.translate('xpack.uptime.featureCatalogueDescription', { @@ -20,4 +22,4 @@ FeatureCatalogueRegistryProvider.register(() => ({ path: `uptime#/`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, -})); +}); diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js index 3b25084e70e95..eca130b4d7465 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js @@ -17,6 +17,7 @@ function mockGetXPackLicense(callCluster, license, req) { path: '/_license', query: { local: 'true', + accept_enterprise: 'true', }, }) .returns( @@ -32,6 +33,7 @@ function mockGetXPackLicense(callCluster, license, req) { path: '/_license', query: { local: 'true', + accept_enterprise: 'true', }, }) // conveniently wraps the passed in license object as { license: response }, like it really is diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js index 925d573b490b8..aaeb890981aa1 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js @@ -23,6 +23,8 @@ export function getXPackLicense(callCluster) { query: { // Fetching the local license is cheaper than getting it from the master and good enough local: 'true', + // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. + accept_enterprise: 'true', }, }).then(({ license }) => license); } diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index aa6f665e35255..c3ca0a16df797 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -404,8 +404,8 @@ The webhook action uses [axios](https://github.com/axios/axios) to send a POST o |Property|Description|Type| |---|---|---| -|user|Username for HTTP Basic authentication|string| -|password|Password for HTTP Basic authentication|string| +|user|Username for HTTP Basic authentication|string _(optional)_| +|password|Password for HTTP Basic authentication|string _(optional)_| ### `params` diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index ae1d8c3fddc8b..09ab6af47e443 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -4,15 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('axios', () => ({ + request: jest.fn(), +})); + import { getActionType } from './webhook'; +import { ActionType, Services } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { configUtilsMock } from '../actions_config.mock'; -import { ActionType } from '../types'; import { createActionTypeRegistry } from './index.test'; import { Logger } from '../../../../../src/core/server'; +import axios from 'axios'; + +const axiosRequestMock = axios.request as jest.Mock; const ACTION_TYPE_ID = '.webhook'; +const services: Services = { + callCluster: async (path: string, opts: any) => {}, + savedObjectsClient: savedObjectsClientMock.create(), +}; + let actionType: ActionType; let mockedLogger: jest.Mocked; @@ -38,20 +51,18 @@ describe('secrets validation', () => { expect(validateSecrets(actionType, secrets)).toEqual(secrets); }); - test('fails when secret password is omitted', () => { + test('fails when secret user is provided, but password is omitted', () => { expect(() => { validateSecrets(actionType, { user: 'bob' }); }).toThrowErrorMatchingInlineSnapshot( - `"error validating action type secrets: [password]: expected value of type [string] but got [undefined]"` + `"error validating action type secrets: both user and password must be specified"` ); }); - test('fails when secret user is omitted', () => { + test('succeeds when basic authentication credentials are omitted', () => { expect(() => { - validateSecrets(actionType, {}); - }).toThrowErrorMatchingInlineSnapshot( - `"error validating action type secrets: [user]: expected value of type [string] but got [undefined]"` - ); + validateSecrets(actionType, {}).toEqual({}); + }); }); }); @@ -190,3 +201,82 @@ describe('params validation', () => { }); }); }); + +describe('execute()', () => { + beforeAll(() => { + axiosRequestMock.mockReset(); + actionType = getActionType({ + logger: mockedLogger, + configurationUtilities: configUtilsMock, + }); + }); + + beforeEach(() => { + axiosRequestMock.mockReset(); + axiosRequestMock.mockResolvedValue({ + status: 200, + statusText: '', + data: '', + headers: [], + config: {}, + }); + }); + + test('execute with username/password sends request with basic auth', async () => { + await actionType.executor({ + actionId: 'some-id', + services, + config: { + url: 'https://abc.def/my-webhook', + method: 'post', + headers: { + aheader: 'a value', + }, + }, + secrets: { user: 'abc', password: '123' }, + params: { body: 'some data' }, + }); + + expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "auth": Object { + "password": "123", + "username": "abc", + }, + "data": "some data", + "headers": Object { + "aheader": "a value", + }, + "method": "post", + "url": "https://abc.def/my-webhook", + } + `); + }); + + test('execute without username/password sends request without basic auth', async () => { + await actionType.executor({ + actionId: 'some-id', + services, + config: { + url: 'https://abc.def/my-webhook', + method: 'post', + headers: { + aheader: 'a value', + }, + }, + secrets: {}, + params: { body: 'some data' }, + }); + + expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "data": "some data", + "headers": Object { + "aheader": "a value", + }, + "method": "post", + "url": "https://abc.def/my-webhook", + } + `); + }); +}); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index f7efb3b1e746c..e275deace0dcc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { curry } from 'lodash'; +import { curry, isString } from 'lodash'; import axios, { AxiosError, AxiosResponse } from 'axios'; import { schema, TypeOf } from '@kbn/config-schema'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -34,10 +34,20 @@ const ConfigSchema = schema.object(configSchemaProps); type ActionTypeConfigType = TypeOf; // secrets definition -type ActionTypeSecretsType = TypeOf; -const SecretsSchema = schema.object({ - user: schema.string(), - password: schema.string(), +export type ActionTypeSecretsType = TypeOf; +const secretSchemaProps = { + user: schema.nullable(schema.string()), + password: schema.nullable(schema.string()), +}; +const SecretsSchema = schema.object(secretSchemaProps, { + validate: secrets => { + // user and password must be set together (or not at all) + if (!secrets.password && !secrets.user) return; + if (secrets.password && secrets.user) return; + return i18n.translate('xpack.actions.builtin.webhook.invalidUsernamePassword', { + defaultMessage: 'both user and password must be specified', + }); + }, }); // params definition @@ -61,7 +71,7 @@ export function getActionType({ }), validate: { config: schema.object(configSchemaProps, { - validate: curry(valdiateActionTypeConfig)(configurationUtilities), + validate: curry(validateActionTypeConfig)(configurationUtilities), }), secrets: SecretsSchema, params: ParamsSchema, @@ -70,7 +80,7 @@ export function getActionType({ }; } -function valdiateActionTypeConfig( +function validateActionTypeConfig( configurationUtilities: ActionsConfigurationUtilities, configObject: ActionTypeConfigType ) { @@ -93,17 +103,19 @@ export async function executor( ): Promise { const actionId = execOptions.actionId; const { method, url, headers = {} } = execOptions.config as ActionTypeConfigType; - const { user: username, password } = execOptions.secrets as ActionTypeSecretsType; const { body: data } = execOptions.params as ActionParamsType; + const secrets: ActionTypeSecretsType = execOptions.secrets as ActionTypeSecretsType; + const basicAuth = + isString(secrets.user) && isString(secrets.password) + ? { auth: { username: secrets.user, password: secrets.password } } + : {}; + const result: Result = await promiseResult( axios.request({ method, url, - auth: { - username, - password, - }, + ...basicAuth, headers, data, }) diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 7d4233db0f8d9..8301a13c82469 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -10,7 +10,7 @@ import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; -import { createEventLoggerMock } from '../../../event_log/server/event_logger.mock'; +import { eventLoggerMock } from '../../../event_log/server/mocks'; const actionExecutor = new ActionExecutor(); const savedObjectsClient = savedObjectsClientMock.create(); @@ -41,7 +41,7 @@ actionExecutor.initialize({ getServices, actionTypeRegistry, encryptedSavedObjectsPlugin, - eventLogger: createEventLoggerMock(), + eventLogger: eventLoggerMock.create(), }); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 8890de2483290..fda1e2f5d2456 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -13,7 +13,7 @@ import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; import { savedObjectsClientMock, loggingServiceMock } from 'src/core/server/mocks'; -import { createEventLoggerMock } from '../../../event_log/server/event_logger.mock'; +import { eventLoggerMock } from '../../../event_log/server/mocks'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -59,7 +59,7 @@ const actionExecutorInitializerParams = { getServices: jest.fn().mockReturnValue(services), actionTypeRegistry, encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, - eventLogger: createEventLoggerMock(), + eventLogger: eventLoggerMock.create(), }; const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts index d993c3d8ad51d..b0a3f64c6a479 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts @@ -5,14 +5,14 @@ */ import { setupGetConjunctionSuggestions } from './conjunction'; -import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestionGetFnArgs, esKuery } from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial) => (kueryNode as unknown) as esKuery.KueryNode; describe('Kuery conjunction suggestions', () => { - const querySuggestionsArgs = (null as unknown) as autocomplete.QuerySuggestionsGetFnArgs; + const querySuggestionsArgs = (null as unknown) as QuerySuggestionGetFnArgs; let getSuggestions: ReturnType; beforeEach(() => { diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx index fa655562134cc..fedb43812d3d0 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx @@ -7,7 +7,10 @@ import React from 'react'; import { $Keys } from 'utility-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { KqlQuerySuggestionProvider } from './types'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { + QuerySuggestion, + QuerySuggestionTypes, +} from '../../../../../../../src/plugins/data/public'; const bothArgumentsText = ( = { export const setupGetConjunctionSuggestions: KqlQuerySuggestionProvider = core => { return (querySuggestionsArgs, { text, end }) => { - let suggestions: autocomplete.QuerySuggestion[] | [] = []; + let suggestions: QuerySuggestion[] | [] = []; if (text.endsWith(' ')) { suggestions = Object.keys(conjunctions).map((key: $Keys) => ({ - type: autocomplete.QuerySuggestionsTypes.Conjunction, + type: QuerySuggestionTypes.Conjunction, text: `${key} `, description: conjunctions[key], start: end, diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts index d05fd49d266f2..00262d092947b 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts @@ -6,20 +6,24 @@ import indexPatternResponse from './__fixtures__/index_pattern_response.json'; import { setupGetFieldSuggestions } from './field'; -import { isFilterable, autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { + isFilterable, + QuerySuggestionGetFnArgs, + esKuery, +} from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial) => (kueryNode as unknown) as esKuery.KueryNode; describe('Kuery field suggestions', () => { - let querySuggestionsArgs: autocomplete.QuerySuggestionsGetFnArgs; + let querySuggestionsArgs: QuerySuggestionGetFnArgs; let getSuggestions: ReturnType; beforeEach(() => { querySuggestionsArgs = ({ indexPatterns: [indexPatternResponse], - } as unknown) as autocomplete.QuerySuggestionsGetFnArgs; + } as unknown) as QuerySuggestionGetFnArgs; getSuggestions = setupGetFieldSuggestions(coreMock.createSetup()); }); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx index f04312b925436..0dcbea893ace4 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx @@ -11,7 +11,8 @@ import { sortPrefixFirst } from './sort_prefix_first'; import { IFieldType, isFilterable, - autocomplete, + QuerySuggestionField, + QuerySuggestionTypes, } from '../../../../../../../src/plugins/data/public'; import { KqlQuerySuggestionProvider } from './types'; @@ -38,7 +39,7 @@ const keywordComparator = (first: IFieldType, second: IFieldType) => { return first.name.localeCompare(second.name); }; -export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = core => { +export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = core => { return ({ indexPatterns }, { start, end, prefix, suffix, nestedPath = '' }) => { const allFields = flatten( indexPatterns.map(indexPattern => { @@ -59,7 +60,7 @@ export const setupGetFieldSuggestions: KqlQuerySuggestionProvider { + const suggestions: QuerySuggestionField[] = sortedFields.map(field => { const remainingPath = field.subType && field.subType.nested ? field.subType.nested.path.slice(nestedPath ? nestedPath.length + 1 : 0) @@ -77,7 +78,7 @@ export const setupGetFieldSuggestions: KqlQuerySuggestionProvider +const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] => uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); export const KUERY_LANGUAGE_NAME = 'kuery'; -export const setupKqlQuerySuggestionProvider = ( - core: CoreSetup -): autocomplete.QuerySuggestionsGetFn => { +export const setupKqlQuerySuggestionProvider = (core: CoreSetup): QuerySuggestionGetFn => { const providers = { field: setupGetFieldSuggestions(core), value: setupGetValueSuggestions(core), @@ -32,8 +35,8 @@ export const setupKqlQuerySuggestionProvider = ( const getSuggestionsByType = ( cursoredQuery: string, - querySuggestionsArgs: autocomplete.QuerySuggestionsGetFnArgs - ): Array> | [] => { + querySuggestionsArgs: QuerySuggestionGetFnArgs + ): Array> | [] => { try { const cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts index 7e564b96064ef..186d455a518b4 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts @@ -6,7 +6,7 @@ import indexPatternResponse from './__fixtures__/index_pattern_response.json'; import { setupGetOperatorSuggestions } from './operator'; -import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestionGetFnArgs, esKuery } from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial) => @@ -14,12 +14,12 @@ const mockKueryNode = (kueryNode: Partial) => describe('Kuery operator suggestions', () => { let getSuggestions: ReturnType; - let querySuggestionsArgs: autocomplete.QuerySuggestionsGetFnArgs; + let querySuggestionsArgs: QuerySuggestionGetFnArgs; beforeEach(() => { querySuggestionsArgs = ({ indexPatterns: [indexPatternResponse], - } as unknown) as autocomplete.QuerySuggestionsGetFnArgs; + } as unknown) as QuerySuggestionGetFnArgs; getSuggestions = setupGetOperatorSuggestions(coreMock.createSetup()); }); diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx index af90e7bfe1172..14c42d73f8d0b 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx @@ -10,7 +10,7 @@ import { $Keys } from 'utility-types'; import { flatten } from 'lodash'; import { KqlQuerySuggestionProvider } from './types'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestionTypes } from '../../../../../../../src/plugins/data/public'; const equalsText = ( { }); const suggestions = matchingOperators.map(operator => ({ - type: autocomplete.QuerySuggestionsTypes.Operator, + type: QuerySuggestionTypes.Operator, text: operator + ' ', description: getDescription(operator), start: end, diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts index 8e3146ab09848..eb7582fc6ec6b 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts @@ -5,11 +5,15 @@ */ import { CoreSetup } from 'kibana/public'; -import { esKuery, autocomplete } from '../../../../../../../src/plugins/data/public'; +import { + esKuery, + QuerySuggestionBasic, + QuerySuggestionGetFnArgs, +} from '../../../../../../../src/plugins/data/public'; -export type KqlQuerySuggestionProvider = ( +export type KqlQuerySuggestionProvider = ( core: CoreSetup ) => ( - querySuggestionsGetFnArgs: autocomplete.QuerySuggestionsGetFnArgs, + querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: esKuery.KueryNode ) => Promise; diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts index 14eeabda97d1a..41fee5fa930fd 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts @@ -6,7 +6,7 @@ import { setupGetValueSuggestions } from './value'; import indexPatternResponse from './__fixtures__/index_pattern_response.json'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { QuerySuggestionGetFnArgs, esKuery } from '../../../../../../../src/plugins/data/public'; import { setAutocompleteService } from '../../../services'; const mockKueryNode = (kueryNode: Partial) => @@ -14,14 +14,14 @@ const mockKueryNode = (kueryNode: Partial) => describe('Kuery value suggestions', () => { let getSuggestions: ReturnType; - let querySuggestionsArgs: autocomplete.QuerySuggestionsGetFnArgs; + let querySuggestionsArgs: QuerySuggestionGetFnArgs; let autocompleteServiceMock: any; beforeEach(() => { getSuggestions = setupGetValueSuggestions(coreMock.createSetup()); querySuggestionsArgs = ({ indexPatterns: [indexPatternResponse], - } as unknown) as autocomplete.QuerySuggestionsGetFnArgs; + } as unknown) as QuerySuggestionGetFnArgs; autocompleteServiceMock = { getValueSuggestions: jest.fn(({ field }) => { diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts index 83b8024d8314d..bfd1e13ad9c39 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -8,13 +8,16 @@ import { flatten } from 'lodash'; import { escapeQuotes } from './lib/escape_kuery'; import { KqlQuerySuggestionProvider } from './types'; import { getAutocompleteService } from '../../../services'; -import { autocomplete } from '../../../../../../../src/plugins/data/public'; +import { + QuerySuggestion, + QuerySuggestionTypes, +} from '../../../../../../../src/plugins/data/public'; const wrapAsSuggestions = (start: number, end: number, query: string, values: string[]) => values .filter(value => value.toLowerCase().includes(query.toLowerCase())) .map(value => ({ - type: autocomplete.QuerySuggestionsTypes.Value, + type: QuerySuggestionTypes.Value, text: `${value} `, start, end, @@ -24,7 +27,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = core => { return async ( { indexPatterns, boolFilter, signal }, { start, end, prefix, suffix, fieldName, nestedPath } - ): Promise => { + ): Promise => { const allFields = flatten( indexPatterns.map(indexPattern => indexPattern.fields.map(field => ({ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index aede95ceb3759..11bac195653c6 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -7,7 +7,7 @@ import qs from 'querystring'; import { HttpFetchQuery } from 'src/core/public'; import { AppAction } from '../action'; -import { MiddlewareFactory } from '../../types'; +import { MiddlewareFactory, AlertListData } from '../../types'; export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { const qp = qs.parse(window.location.search.slice(1)); @@ -15,7 +15,7 @@ export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { return api => next => async (action: AppAction) => { next(action); if (action.type === 'userNavigatedToPage' && action.payload === 'alertsPage') { - const response = await coreStart.http.get('/api/endpoint/alerts', { + const response: AlertListData = await coreStart.http.get('/api/endpoint/alerts', { query: qp as HttpFetchQuery, }); api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index fd74abe9e3432..de79476245d29 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -25,7 +25,7 @@ export const alertListReducer: Reducer = ( if (action.type === 'serverReturnedAlertsData') { return { ...state, - alerts: action.payload.alerts, + ...action.payload, }; } diff --git a/x-pack/plugins/event_log/server/event_log_service.mock.ts b/x-pack/plugins/event_log/server/event_log_service.mock.ts new file mode 100644 index 0000000000000..805c241414a2e --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_service.mock.ts @@ -0,0 +1,25 @@ +/* + * 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 { IEventLogService } from './types'; +import { eventLoggerMock } from './event_logger.mock'; + +const createEventLogServiceMock = () => { + const mock: jest.Mocked = { + isEnabled: jest.fn(), + isLoggingEntries: jest.fn(), + isIndexingEntries: jest.fn(), + registerProviderActions: jest.fn(), + isProviderActionRegistered: jest.fn(), + getProviderActions: jest.fn(), + getLogger: jest.fn().mockReturnValue(eventLoggerMock.create()), + }; + return mock; +}; + +export const eventLogServiceMock = { + create: createEventLogServiceMock, +}; diff --git a/x-pack/plugins/event_log/server/event_logger.mock.ts b/x-pack/plugins/event_log/server/event_logger.mock.ts index 97c2b9f980dcd..6a2c10b625b8e 100644 --- a/x-pack/plugins/event_log/server/event_logger.mock.ts +++ b/x-pack/plugins/event_log/server/event_logger.mock.ts @@ -4,12 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEvent, IEventLogger } from './types'; +import { IEventLogger } from './types'; -export function createEventLoggerMock(): IEventLogger { - return { - logEvent(eventProperties: IEvent): void {}, - startTiming(event: IEvent): void {}, - stopTiming(event: IEvent): void {}, +const createEventLoggerMock = () => { + const mock: jest.Mocked = { + logEvent: jest.fn(), + startTiming: jest.fn(), + stopTiming: jest.fn(), }; -} + return mock; +}; + +export const eventLoggerMock = { + create: createEventLoggerMock, +}; diff --git a/x-pack/plugins/event_log/server/mocks.ts b/x-pack/plugins/event_log/server/mocks.ts new file mode 100644 index 0000000000000..aad6cf3e24561 --- /dev/null +++ b/x-pack/plugins/event_log/server/mocks.ts @@ -0,0 +1,23 @@ +/* + * 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 { eventLogServiceMock } from './event_log_service.mock'; + +export { eventLogServiceMock }; +export { eventLoggerMock } from './event_logger.mock'; + +const createSetupMock = () => { + return eventLogServiceMock.create(); +}; + +const createStartMock = () => { + return undefined; +}; + +export const eventLogMock = { + createSetup: createSetupMock, + createStart: createStartMock, +}; diff --git a/x-pack/plugins/remote_clusters/common/constants.ts b/x-pack/plugins/remote_clusters/common/constants.ts new file mode 100644 index 0000000000000..3521b7f662fc9 --- /dev/null +++ b/x-pack/plugins/remote_clusters/common/constants.ts @@ -0,0 +1,23 @@ +/* + * 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 { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +export const PLUGIN = { + id: 'remote_clusters', + // Remote Clusters are used in both CCS and CCR, and CCS is available for all licenses. + minimumLicenseType: basicLicense, + getI18nName: (): string => { + return i18n.translate('xpack.remoteClusters.appName', { + defaultMessage: 'Remote Clusters', + }); + }, +}; + +export const API_BASE_PATH = '/api/remote_clusters'; diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts new file mode 100644 index 0000000000000..476fbee7fb6a0 --- /dev/null +++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts @@ -0,0 +1,137 @@ +/* + * 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 { deserializeCluster, serializeCluster } from './cluster_serialization'; + +describe('cluster_serialization', () => { + describe('deserializeCluster()', () => { + it('should throw an error for invalid arguments', () => { + expect(() => deserializeCluster('foo', 'bar')).toThrowError(); + }); + + it('should deserialize a complete cluster object', () => { + expect( + deserializeCluster('test_cluster', { + seeds: ['localhost:9300'], + connected: true, + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + transport: { + ping_schedule: '-1', + compress: false, + }, + }) + ).toEqual({ + name: 'test_cluster', + seeds: ['localhost:9300'], + isConnected: true, + connectedNodesCount: 1, + maxConnectionsPerCluster: 3, + initialConnectTimeout: '30s', + skipUnavailable: false, + transportPingSchedule: '-1', + transportCompress: false, + }); + }); + + it('should deserialize a cluster object without transport information', () => { + expect( + deserializeCluster('test_cluster', { + seeds: ['localhost:9300'], + connected: true, + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }) + ).toEqual({ + name: 'test_cluster', + seeds: ['localhost:9300'], + isConnected: true, + connectedNodesCount: 1, + maxConnectionsPerCluster: 3, + initialConnectTimeout: '30s', + skipUnavailable: false, + }); + }); + + it('should deserialize a cluster object with arbitrary missing properties', () => { + expect( + deserializeCluster('test_cluster', { + seeds: ['localhost:9300'], + connected: true, + num_nodes_connected: 1, + initial_connect_timeout: '30s', + transport: { + compress: false, + }, + }) + ).toEqual({ + name: 'test_cluster', + seeds: ['localhost:9300'], + isConnected: true, + connectedNodesCount: 1, + initialConnectTimeout: '30s', + transportCompress: false, + }); + }); + }); + + describe('serializeCluster()', () => { + it('should throw an error for invalid arguments', () => { + expect(() => serializeCluster('foo')).toThrowError(); + }); + + it('should serialize a complete cluster object to only dynamic properties', () => { + expect( + serializeCluster({ + name: 'test_cluster', + seeds: ['localhost:9300'], + isConnected: true, + connectedNodesCount: 1, + maxConnectionsPerCluster: 3, + initialConnectTimeout: '30s', + skipUnavailable: false, + transportPingSchedule: '-1', + transportCompress: false, + }) + ).toEqual({ + persistent: { + cluster: { + remote: { + test_cluster: { + seeds: ['localhost:9300'], + skip_unavailable: false, + }, + }, + }, + }, + }); + }); + + it('should serialize a cluster object with missing properties', () => { + expect( + serializeCluster({ + name: 'test_cluster', + seeds: ['localhost:9300'], + }) + ).toEqual({ + persistent: { + cluster: { + remote: { + test_cluster: { + seeds: ['localhost:9300'], + skip_unavailable: null, + }, + }, + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts new file mode 100644 index 0000000000000..07ea79d42b800 --- /dev/null +++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function deserializeCluster(name: string, esClusterObject: any): any { + if (!name || !esClusterObject || typeof esClusterObject !== 'object') { + throw new Error('Unable to deserialize cluster'); + } + + const { + seeds, + connected: isConnected, + num_nodes_connected: connectedNodesCount, + max_connections_per_cluster: maxConnectionsPerCluster, + initial_connect_timeout: initialConnectTimeout, + skip_unavailable: skipUnavailable, + transport, + } = esClusterObject; + + let deserializedClusterObject: any = { + name, + seeds, + isConnected, + connectedNodesCount, + maxConnectionsPerCluster, + initialConnectTimeout, + skipUnavailable, + }; + + if (transport) { + const { ping_schedule: transportPingSchedule, compress: transportCompress } = transport; + + deserializedClusterObject = { + ...deserializedClusterObject, + transportPingSchedule, + transportCompress, + }; + } + + // It's unnecessary to send undefined values back to the client, so we can remove them. + Object.keys(deserializedClusterObject).forEach(key => { + if (deserializedClusterObject[key] === undefined) { + delete deserializedClusterObject[key]; + } + }); + + return deserializedClusterObject; +} + +export function serializeCluster(deserializedClusterObject: any): any { + if (!deserializedClusterObject || typeof deserializedClusterObject !== 'object') { + throw new Error('Unable to serialize cluster'); + } + + const { name, seeds, skipUnavailable } = deserializedClusterObject; + + return { + persistent: { + cluster: { + remote: { + [name]: { + seeds: seeds ? seeds : null, + skip_unavailable: skipUnavailable !== undefined ? skipUnavailable : null, + }, + }, + }, + }, + }; +} diff --git a/x-pack/legacy/plugins/spaces/common/index.ts b/x-pack/plugins/remote_clusters/common/lib/index.ts similarity index 69% rename from x-pack/legacy/plugins/spaces/common/index.ts rename to x-pack/plugins/remote_clusters/common/lib/index.ts index 8961c9c5ccf79..bc67bf21af038 100644 --- a/x-pack/legacy/plugins/spaces/common/index.ts +++ b/x-pack/plugins/remote_clusters/common/lib/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isReservedSpace } from './is_reserved_space'; -export { MAX_SPACE_INITIALS } from './constants'; +export { deserializeCluster, serializeCluster } from './cluster_serialization'; diff --git a/x-pack/plugins/remote_clusters/kibana.json b/x-pack/plugins/remote_clusters/kibana.json new file mode 100644 index 0000000000000..de1e3d1e26865 --- /dev/null +++ b/x-pack/plugins/remote_clusters/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "remote_clusters", + "version": "kibana", + "requiredPlugins": [ + "licensing" + ], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/remote_clusters/server/index.ts b/x-pack/plugins/remote_clusters/server/index.ts new file mode 100644 index 0000000000000..896161d82919b --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { PluginInitializerContext } from 'kibana/server'; +import { RemoteClustersServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new RemoteClustersServerPlugin(ctx); diff --git a/x-pack/legacy/plugins/remote_clusters/server/lib/does_cluster_exist.ts b/x-pack/plugins/remote_clusters/server/lib/does_cluster_exist.ts similarity index 70% rename from x-pack/legacy/plugins/remote_clusters/server/lib/does_cluster_exist.ts rename to x-pack/plugins/remote_clusters/server/lib/does_cluster_exist.ts index 1e450cf4ae920..8f3e828f79086 100644 --- a/x-pack/legacy/plugins/remote_clusters/server/lib/does_cluster_exist.ts +++ b/x-pack/plugins/remote_clusters/server/lib/does_cluster_exist.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export async function doesClusterExist(callWithRequest: any, clusterName: string): Promise { +export async function doesClusterExist(callAsCurrentUser: any, clusterName: string): Promise { try { - const clusterInfoByName = await callWithRequest('cluster.remoteInfo'); + const clusterInfoByName = await callAsCurrentUser('cluster.remoteInfo'); return Boolean(clusterInfoByName && clusterInfoByName[clusterName]); } catch (err) { throw new Error('Unable to check if cluster already exists.'); diff --git a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature/index.js b/x-pack/plugins/remote_clusters/server/lib/is_es_error/index.ts old mode 100755 new mode 100644 similarity index 84% rename from x-pack/legacy/plugins/logstash/public/lib/register_home_feature/index.js rename to x-pack/plugins/remote_clusters/server/lib/is_es_error/index.ts index 72e3f201bd4ca..a9a3c61472d8c --- a/x-pack/legacy/plugins/logstash/public/lib/register_home_feature/index.js +++ b/x-pack/plugins/remote_clusters/server/lib/is_es_error/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './register_home_feature'; +export { isEsError } from './is_es_error'; diff --git a/x-pack/plugins/remote_clusters/server/lib/is_es_error/is_es_error.ts b/x-pack/plugins/remote_clusters/server/lib/is_es_error/is_es_error.ts new file mode 100644 index 0000000000000..4137293cf39c0 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/lib/is_es_error/is_es_error.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; +} diff --git a/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/index.ts b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/index.ts new file mode 100644 index 0000000000000..0743e443955f4 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/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 { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.ts b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.ts new file mode 100644 index 0000000000000..ff777698599cf --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.ts @@ -0,0 +1,47 @@ +/* + * 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 expect from '@kbn/expect'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; +import { licensePreRoutingFactory } from '../license_pre_routing_factory'; +import { LicenseStatus } from '../../types'; + +describe('licensePreRoutingFactory()', () => { + let mockDeps: any; + let mockContext: any; + let licenseStatus: LicenseStatus; + + beforeEach(() => { + mockDeps = { getLicenseStatus: () => licenseStatus }; + mockContext = { + core: {}, + actions: {}, + licensing: {}, + }; + }); + + describe('status is not valid', () => { + it('replies with 403', () => { + licenseStatus = { valid: false }; + const stubRequest: any = {}; + const stubHandler: any = () => {}; + const routeWithLicenseCheck = licensePreRoutingFactory(mockDeps, stubHandler); + const response: any = routeWithLicenseCheck(mockContext, stubRequest, kibanaResponseFactory); + expect(response.status).to.be(403); + }); + }); + + describe('status is valid', () => { + it('replies with nothing', () => { + licenseStatus = { valid: true }; + const stubRequest: any = {}; + const stubHandler: any = () => null; + const routeWithLicenseCheck = licensePreRoutingFactory(mockDeps, stubHandler); + const response = routeWithLicenseCheck(mockContext, stubRequest, kibanaResponseFactory); + expect(response).to.be(null); + }); + }); +}); diff --git a/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts new file mode 100644 index 0000000000000..09d78302a7e76 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts @@ -0,0 +1,35 @@ +/* + * 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 { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'kibana/server'; +import { RouteDependencies } from '../../types'; + +export const licensePreRoutingFactory = ( + { getLicenseStatus }: RouteDependencies, + handler: RequestHandler +) => { + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = getLicenseStatus(); + if (!licenseStatus.valid) { + return response.forbidden({ + body: { + message: licenseStatus.message || '', + }, + }); + } + + return handler(ctx, request, response); + }; +}; diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts new file mode 100644 index 0000000000000..dd0bb536d2695 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -0,0 +1,72 @@ +/* + * 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 { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; +import { PLUGIN } from '../common/constants'; +import { LICENSE_CHECK_STATE } from '../../licensing/common/types'; +import { Dependencies, LicenseStatus, RouteDependencies } from './types'; + +import { + registerGetRoute, + registerAddRoute, + registerUpdateRoute, + registerDeleteRoute, +} from './routes/api'; + +export class RemoteClustersServerPlugin implements Plugin { + licenseStatus: LicenseStatus; + log: Logger; + + constructor({ logger }: PluginInitializerContext) { + this.log = logger.get(); + this.licenseStatus = { valid: false }; + } + + async setup( + { http, elasticsearch: elasticsearchService }: CoreSetup, + { licensing }: Dependencies + ) { + const elasticsearch = await elasticsearchService.adminClient; + const router = http.createRouter(); + const routeDependencies: RouteDependencies = { + elasticsearch, + elasticsearchService, + router, + getLicenseStatus: () => this.licenseStatus, + }; + + // Register routes + registerGetRoute(routeDependencies); + registerAddRoute(routeDependencies); + registerUpdateRoute(routeDependencies); + registerDeleteRoute(routeDependencies); + + licensing.license$.subscribe(license => { + const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType); + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + if (hasRequiredLicense) { + this.licenseStatus = { valid: true }; + } else { + this.licenseStatus = { + valid: false, + message: + message || + i18n.translate('xpack.remoteClusters.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }; + if (message) { + this.log.info(message); + } + } + }); + } + + start() {} + + stop() {} +} diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts new file mode 100644 index 0000000000000..a6edd15995d72 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts @@ -0,0 +1,194 @@ +/* + * 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 { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { register } from './add_route'; +import { API_BASE_PATH } from '../../../common/constants'; +import { LicenseStatus } from '../../types'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, +} from '../../../../../../src/core/server/mocks'; + +interface TestOptions { + licenseCheckResult?: LicenseStatus; + apiResponses?: Array<() => Promise>; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; + payload?: Record; +} + +describe('ADD remote clusters', () => { + const addRemoteClustersTest = ( + description: string, + { licenseCheckResult = { valid: true }, apiResponses = [], asserts, payload }: TestOptions + ) => { + test(description, async () => { + const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + + const mockRouteDependencies = { + router: httpServiceMock.createRouter(), + getLicenseStatus: () => licenseCheckResult, + elasticsearchService: elasticsearchServiceMock.createInternalSetup(), + elasticsearch: elasticsearchMock, + }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + + elasticsearchServiceMock + .createClusterClient() + .asScoped.mockReturnValue(mockScopedClusterClient); + + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + register(mockRouteDependencies); + const [[{ validate }, handler]] = mockRouteDependencies.router.post.mock.calls; + + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: API_BASE_PATH, + body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + headers: { authorization: 'foo' }, + }); + + const mockContext = ({ + core: { + elasticsearch: { + dataClient: mockScopedClusterClient, + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + }); + }; + + describe('success', () => { + addRemoteClustersTest('adds remote cluster', { + apiResponses: [ + async () => ({}), + async () => ({ + acknowledged: true, + persistent: { + cluster: { + remote: { + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }, + }, + }, + transient: {}, + }), + ], + payload: { + name: 'test', + seeds: ['127.0.0.1:9300'], + skipUnavailable: false, + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: ['127.0.0.1:9300'], skip_unavailable: false } }, + }, + }, + }, + }, + ], + ], + statusCode: 200, + result: { + acknowledged: true, + }, + }, + }); + }); + + describe('failure', () => { + addRemoteClustersTest('returns 409 if remote cluster already exists', { + apiResponses: [ + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + ], + payload: { + name: 'test', + seeds: ['127.0.0.1:9300'], + skipUnavailable: false, + }, + asserts: { + apiArguments: [['cluster.remoteInfo']], + statusCode: 409, + result: { + message: 'There is already a remote cluster with that name.', + }, + }, + }); + + addRemoteClustersTest('returns 400 ES did not acknowledge remote cluster', { + apiResponses: [async () => ({}), async () => ({})], + payload: { + name: 'test', + seeds: ['127.0.0.1:9300'], + skipUnavailable: false, + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: ['127.0.0.1:9300'], skip_unavailable: false } }, + }, + }, + }, + }, + ], + ], + statusCode: 400, + result: { + message: 'Unable to add cluster, no response returned from ES.', + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts new file mode 100644 index 0000000000000..aa09b6bf45667 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts @@ -0,0 +1,98 @@ +/* + * 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 { get } from 'lodash'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { RequestHandler } from 'src/core/server'; + +import { serializeCluster } from '../../../common/lib'; +import { doesClusterExist } from '../../lib/does_cluster_exist'; +import { API_BASE_PATH } from '../../../common/constants'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { RouteDependencies } from '../../types'; + +const bodyValidation = schema.object({ + name: schema.string(), + seeds: schema.arrayOf(schema.string()), + skipUnavailable: schema.boolean(), +}); + +type RouteBody = TypeOf; + +export const register = (deps: RouteDependencies): void => { + const addHandler: RequestHandler = async ( + ctx, + request, + response + ) => { + try { + const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + + const { name, seeds, skipUnavailable } = request.body; + + // Check if cluster already exists. + const existingCluster = await doesClusterExist(callAsCurrentUser, name); + if (existingCluster) { + return response.customError({ + statusCode: 409, + body: { + message: i18n.translate( + 'xpack.remoteClusters.addRemoteCluster.existingRemoteClusterErrorMessage', + { + defaultMessage: 'There is already a remote cluster with that name.', + } + ), + }, + }); + } + + const addClusterPayload = serializeCluster({ name, seeds, skipUnavailable }); + const updateClusterResponse = await callAsCurrentUser('cluster.putSettings', { + body: addClusterPayload, + }); + const acknowledged = get(updateClusterResponse, 'acknowledged'); + const cluster = get(updateClusterResponse, `persistent.cluster.remote.${name}`); + + if (acknowledged && cluster) { + return response.ok({ + body: { + acknowledged: true, + }, + }); + } + + // If for some reason the ES response did not acknowledge, + // return an error. This shouldn't happen. + return response.customError({ + statusCode: 400, + body: { + message: i18n.translate( + 'xpack.remoteClusters.addRemoteCluster.unknownRemoteClusterErrorMessage', + { + defaultMessage: 'Unable to add cluster, no response returned from ES.', + } + ), + }, + }); + } catch (error) { + if (isEsError(error)) { + return response.customError({ statusCode: error.statusCode, body: error }); + } + return response.internalError({ body: error }); + } + }; + deps.router.post( + { + path: API_BASE_PATH, + validate: { + body: bodyValidation, + }, + }, + licensePreRoutingFactory(deps, addHandler) + ); +}; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts new file mode 100644 index 0000000000000..04deb62d2c2d2 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts @@ -0,0 +1,246 @@ +/* + * 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 { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { register } from './delete_route'; +import { API_BASE_PATH } from '../../../common/constants'; +import { LicenseStatus } from '../../types'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, +} from '../../../../../../src/core/server/mocks'; + +interface TestOptions { + licenseCheckResult?: LicenseStatus; + apiResponses?: Array<() => Promise>; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; + params: { + nameOrNames: string; + }; +} + +describe('DELETE remote clusters', () => { + const deleteRemoteClustersTest = ( + description: string, + { licenseCheckResult = { valid: true }, apiResponses = [], asserts, params }: TestOptions + ) => { + test(description, async () => { + const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + + const mockRouteDependencies = { + router: httpServiceMock.createRouter(), + getLicenseStatus: () => licenseCheckResult, + elasticsearchService: elasticsearchServiceMock.createInternalSetup(), + elasticsearch: elasticsearchMock, + }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + + elasticsearchServiceMock + .createClusterClient() + .asScoped.mockReturnValue(mockScopedClusterClient); + + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + register(mockRouteDependencies); + const [[{ validate }, handler]] = mockRouteDependencies.router.delete.mock.calls; + + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `${API_BASE_PATH}/{nameOrNames}`, + params: (validate as any).params.validate(params), + headers: { authorization: 'foo' }, + }); + + const mockContext = ({ + core: { + elasticsearch: { + dataClient: mockScopedClusterClient, + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + }); + }; + + describe('success', () => { + deleteRemoteClustersTest('deletes remote cluster', { + apiResponses: [ + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + async () => ({ + acknowledged: true, + persistent: {}, + transient: {}, + }), + ], + params: { + nameOrNames: 'test', + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: null, skip_unavailable: null } }, + }, + }, + }, + }, + ], + ], + statusCode: 200, + result: { + itemsDeleted: ['test'], + errors: [], + }, + }, + }); + }); + + describe('failure', () => { + deleteRemoteClustersTest( + 'returns errors array with 404 error if remote cluster does not exist', + { + apiResponses: [async () => ({})], + params: { + nameOrNames: 'test', + }, + asserts: { + apiArguments: [['cluster.remoteInfo']], + statusCode: 200, + result: { + errors: [ + { + error: { + options: { + body: { + message: 'There is no remote cluster with that name.', + }, + statusCode: 404, + }, + payload: { + message: 'There is no remote cluster with that name.', + }, + status: 404, + }, + name: 'test', + }, + ], + itemsDeleted: [], + }, + }, + } + ); + + deleteRemoteClustersTest( + 'returns errors array with 400 error if ES still returns cluster information', + { + apiResponses: [ + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + async () => ({ + acknowledged: true, + persistent: { + cluster: { + remote: { + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: true, + }, + }, + }, + }, + transient: {}, + }), + ], + params: { + nameOrNames: 'test', + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: null, skip_unavailable: null } }, + }, + }, + }, + }, + ], + ], + statusCode: 200, + result: { + errors: [ + { + error: { + options: { + body: { + message: 'Unable to delete cluster, information still returned from ES.', + }, + statusCode: 400, + }, + payload: { + message: 'Unable to delete cluster, information still returned from ES.', + }, + status: 400, + }, + name: 'test', + }, + ], + itemsDeleted: [], + }, + }, + } + ); + }); +}); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts new file mode 100644 index 0000000000000..742780ffed309 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -0,0 +1,138 @@ +/* + * 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 { get } from 'lodash'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { RequestHandler } from 'src/core/server'; + +import { RouteDependencies } from '../../types'; +import { serializeCluster } from '../../../common/lib'; +import { API_BASE_PATH } from '../../../common/constants'; +import { doesClusterExist } from '../../lib/does_cluster_exist'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { isEsError } from '../../lib/is_es_error'; + +const paramsValidation = schema.object({ + nameOrNames: schema.string(), +}); + +type RouteParams = TypeOf; + +export const register = (deps: RouteDependencies): void => { + const deleteHandler: RequestHandler = async ( + ctx, + request, + response + ) => { + try { + const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + + const { nameOrNames } = request.params; + const names = nameOrNames.split(','); + + const itemsDeleted: any[] = []; + const errors: any[] = []; + + // Validator that returns an error if the remote cluster does not exist. + const validateClusterDoesExist = async (name: string) => { + try { + const existingCluster = await doesClusterExist(callAsCurrentUser, name); + if (!existingCluster) { + return response.customError({ + statusCode: 404, + body: { + message: i18n.translate( + 'xpack.remoteClusters.deleteRemoteCluster.noRemoteClusterErrorMessage', + { + defaultMessage: 'There is no remote cluster with that name.', + } + ), + }, + }); + } + } catch (error) { + return response.customError({ statusCode: 400, body: error }); + } + }; + + // Send the request to delete the cluster and return an error if it could not be deleted. + const sendRequestToDeleteCluster = async (name: string) => { + try { + const body = serializeCluster({ name }); + const updateClusterResponse = await callAsCurrentUser('cluster.putSettings', { body }); + const acknowledged = get(updateClusterResponse, 'acknowledged'); + const cluster = get(updateClusterResponse, `persistent.cluster.remote.${name}`); + + // Deletion was successful + if (acknowledged && !cluster) { + return null; + } + + // If for some reason the ES response still returns the cluster information, + // return an error. This shouldn't happen. + return response.customError({ + statusCode: 400, + body: { + message: i18n.translate( + 'xpack.remoteClusters.deleteRemoteCluster.unknownRemoteClusterErrorMessage', + { + defaultMessage: 'Unable to delete cluster, information still returned from ES.', + } + ), + }, + }); + } catch (error) { + if (isEsError(error)) { + return response.customError({ statusCode: error.statusCode, body: error }); + } + return response.internalError({ body: error }); + } + }; + + const deleteCluster = async (clusterName: string) => { + // Validate that the cluster exists. + let error: any = await validateClusterDoesExist(clusterName); + + if (!error) { + // Delete the cluster. + error = await sendRequestToDeleteCluster(clusterName); + } + + if (error) { + errors.push({ name: clusterName, error }); + } else { + itemsDeleted.push(clusterName); + } + }; + + // Delete all our cluster in parallel. + await Promise.all(names.map(deleteCluster)); + + return response.ok({ + body: { + itemsDeleted, + errors, + }, + }); + } catch (error) { + if (isEsError(error)) { + return response.customError({ statusCode: error.statusCode, body: error }); + } + return response.internalError({ body: error }); + } + }; + + deps.router.delete( + { + path: `${API_BASE_PATH}/{nameOrNames}`, + validate: { + params: paramsValidation, + }, + }, + licensePreRoutingFactory(deps, deleteHandler) + ); +}; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts new file mode 100644 index 0000000000000..90955be85859d --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts @@ -0,0 +1,190 @@ +/* + * 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 Boom from 'boom'; + +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { register } from './get_route'; +import { API_BASE_PATH } from '../../../common/constants'; +import { LicenseStatus } from '../../types'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, +} from '../../../../../../src/core/server/mocks'; + +interface TestOptions { + licenseCheckResult?: LicenseStatus; + apiResponses?: Array<() => Promise>; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; +} + +describe('GET remote clusters', () => { + const getRemoteClustersTest = ( + description: string, + { licenseCheckResult = { valid: true }, apiResponses = [], asserts }: TestOptions + ) => { + test(description, async () => { + const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + + const mockRouteDependencies = { + router: httpServiceMock.createRouter(), + getLicenseStatus: () => licenseCheckResult, + elasticsearchService: elasticsearchServiceMock.createInternalSetup(), + elasticsearch: elasticsearchMock, + }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + + elasticsearchServiceMock + .createClusterClient() + .asScoped.mockReturnValue(mockScopedClusterClient); + + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + register(mockRouteDependencies); + const [[, handler]] = mockRouteDependencies.router.get.mock.calls; + + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: API_BASE_PATH, + headers: { authorization: 'foo' }, + }); + + const mockContext = ({ + core: { + elasticsearch: { + dataClient: mockScopedClusterClient, + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + }); + }; + + describe('success', () => { + getRemoteClustersTest('returns remote clusters', { + apiResponses: [ + async () => ({ + persistent: { + cluster: { + remote: { + test: { + seeds: ['127.0.0.1:9300'], + skip_unavailable: false, + }, + }, + }, + }, + transient: {}, + }), + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + ], + asserts: { + apiArguments: [['cluster.getSettings'], ['cluster.remoteInfo']], + statusCode: 200, + result: [ + { + name: 'test', + seeds: ['127.0.0.1:9300'], + isConnected: true, + connectedNodesCount: 1, + maxConnectionsPerCluster: 3, + initialConnectTimeout: '30s', + skipUnavailable: false, + isConfiguredByNode: false, + }, + ], + }, + }); + getRemoteClustersTest('returns an empty array when ES responds with an empty object', { + apiResponses: [async () => ({}), async () => ({})], + asserts: { + apiArguments: [['cluster.getSettings'], ['cluster.remoteInfo']], + statusCode: 200, + result: [], + }, + }); + }); + + describe('failure', () => { + const error = Boom.notAcceptable('test error'); + + getRemoteClustersTest('returns an error if failure to get cluster settings', { + apiResponses: [ + async () => { + throw error; + }, + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + ], + asserts: { + apiArguments: [['cluster.getSettings']], + statusCode: 500, + result: error, + }, + }); + + getRemoteClustersTest('returns an error if failure to get cluster remote info', { + apiResponses: [ + async () => ({ + persistent: { + cluster: { + remote: { + test: { + seeds: ['127.0.0.1:9300'], + skip_unavailable: false, + }, + }, + }, + }, + transient: {}, + }), + async () => { + throw error; + }, + ], + asserts: { + apiArguments: [['cluster.getSettings'], ['cluster.remoteInfo']], + statusCode: 500, + result: error, + }, + }); + }); +}); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts new file mode 100644 index 0000000000000..44b6284109ac5 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -0,0 +1,62 @@ +/* + * 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 { get } from 'lodash'; + +import { RequestHandler } from 'src/core/server'; +import { deserializeCluster } from '../../../common/lib'; +import { API_BASE_PATH } from '../../../common/constants'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { RouteDependencies } from '../../types'; + +export const register = (deps: RouteDependencies): void => { + const allHandler: RequestHandler = async (ctx, request, response) => { + try { + const callAsCurrentUser = await ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const clusterSettings = await callAsCurrentUser('cluster.getSettings'); + + const transientClusterNames = Object.keys( + get(clusterSettings, 'transient.cluster.remote') || {} + ); + const persistentClusterNames = Object.keys( + get(clusterSettings, 'persistent.cluster.remote') || {} + ); + + const clustersByName = await callAsCurrentUser('cluster.remoteInfo'); + const clusterNames = (clustersByName && Object.keys(clustersByName)) || []; + + const body = clusterNames.map((clusterName: string): any => { + const cluster = clustersByName[clusterName]; + const isTransient = transientClusterNames.includes(clusterName); + const isPersistent = persistentClusterNames.includes(clusterName); + // If the cluster hasn't been stored in the cluster state, then it's defined by the + // node's config file. + const isConfiguredByNode = !isTransient && !isPersistent; + + return { + ...deserializeCluster(clusterName, cluster), + isConfiguredByNode, + }; + }); + + return response.ok({ body }); + } catch (error) { + if (isEsError(error)) { + return response.customError({ statusCode: error.statusCode, body: error }); + } + return response.internalError({ body: error }); + } + }; + + deps.router.get( + { + path: API_BASE_PATH, + validate: false, + }, + licensePreRoutingFactory(deps, allHandler) + ); +}; diff --git a/x-pack/legacy/plugins/remote_clusters/server/routes/api/index.ts b/x-pack/plugins/remote_clusters/server/routes/api/index.ts similarity index 100% rename from x-pack/legacy/plugins/remote_clusters/server/routes/api/index.ts rename to x-pack/plugins/remote_clusters/server/routes/api/index.ts diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts new file mode 100644 index 0000000000000..9ba239c3ff661 --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts @@ -0,0 +1,228 @@ +/* + * 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 { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { register } from './update_route'; +import { API_BASE_PATH } from '../../../common/constants'; +import { LicenseStatus } from '../../types'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, +} from '../../../../../../src/core/server/mocks'; + +interface TestOptions { + licenseCheckResult?: LicenseStatus; + apiResponses?: Array<() => Promise>; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; + payload?: Record; + params: { + name: string; + }; +} + +describe('UPDATE remote clusters', () => { + const updateRemoteClustersTest = ( + description: string, + { + licenseCheckResult = { valid: true }, + apiResponses = [], + asserts, + payload, + params, + }: TestOptions + ) => { + test(description, async () => { + const { adminClient: elasticsearchMock } = elasticsearchServiceMock.createSetup(); + + const mockRouteDependencies = { + router: httpServiceMock.createRouter(), + getLicenseStatus: () => licenseCheckResult, + elasticsearchService: elasticsearchServiceMock.createInternalSetup(), + elasticsearch: elasticsearchMock, + }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + + elasticsearchServiceMock + .createClusterClient() + .asScoped.mockReturnValue(mockScopedClusterClient); + + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + register(mockRouteDependencies); + const [[{ validate }, handler]] = mockRouteDependencies.router.put.mock.calls; + + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'put', + path: `${API_BASE_PATH}/{name}`, + params: (validate as any).params.validate(params), + body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + headers: { authorization: 'foo' }, + }); + + const mockContext = ({ + core: { + elasticsearch: { + dataClient: mockScopedClusterClient, + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + }); + }; + + describe('success', () => { + updateRemoteClustersTest('updates remote cluster', { + apiResponses: [ + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + async () => ({ + acknowledged: true, + persistent: { + cluster: { + remote: { + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: true, + }, + }, + }, + }, + transient: {}, + }), + ], + params: { + name: 'test', + }, + payload: { + seeds: ['127.0.0.1:9300'], + skipUnavailable: true, + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: ['127.0.0.1:9300'], skip_unavailable: true } }, + }, + }, + }, + }, + ], + ], + statusCode: 200, + result: { + connectedNodesCount: 1, + initialConnectTimeout: '30s', + isConfiguredByNode: false, + isConnected: true, + maxConnectionsPerCluster: 3, + name: 'test', + seeds: ['127.0.0.1:9300'], + skipUnavailable: true, + }, + }, + }); + }); + + describe('failure', () => { + updateRemoteClustersTest('returns 404 if remote cluster does not exist', { + apiResponses: [async () => ({})], + payload: { + seeds: ['127.0.0.1:9300'], + skipUnavailable: false, + }, + params: { + name: 'test', + }, + asserts: { + apiArguments: [['cluster.remoteInfo']], + statusCode: 404, + result: { + message: 'There is no remote cluster with that name.', + }, + }, + }); + + updateRemoteClustersTest('returns 400 if ES did not acknowledge remote cluster', { + apiResponses: [ + async () => ({ + test: { + connected: true, + mode: 'sniff', + seeds: ['127.0.0.1:9300'], + num_nodes_connected: 1, + max_connections_per_cluster: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + }, + }), + async () => ({}), + ], + payload: { + seeds: ['127.0.0.1:9300'], + skipUnavailable: false, + }, + params: { + name: 'test', + }, + asserts: { + apiArguments: [ + ['cluster.remoteInfo'], + [ + 'cluster.putSettings', + { + body: { + persistent: { + cluster: { + remote: { test: { seeds: ['127.0.0.1:9300'], skip_unavailable: false } }, + }, + }, + }, + }, + ], + ], + statusCode: 400, + result: { + message: 'Unable to edit cluster, no response returned from ES.', + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts new file mode 100644 index 0000000000000..fd707f15ad11e --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts @@ -0,0 +1,108 @@ +/* + * 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 { get } from 'lodash'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { RequestHandler } from 'src/core/server'; + +import { API_BASE_PATH } from '../../../common/constants'; +import { serializeCluster, deserializeCluster } from '../../../common/lib'; +import { doesClusterExist } from '../../lib/does_cluster_exist'; +import { RouteDependencies } from '../../types'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { isEsError } from '../../lib/is_es_error'; + +const bodyValidation = schema.object({ + seeds: schema.arrayOf(schema.string()), + skipUnavailable: schema.boolean(), +}); + +const paramsValidation = schema.object({ + name: schema.string(), +}); + +type RouteParams = TypeOf; + +type RouteBody = TypeOf; + +export const register = (deps: RouteDependencies): void => { + const updateHandler: RequestHandler = async ( + ctx, + request, + response + ) => { + try { + const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + + const { name } = request.params; + const { seeds, skipUnavailable } = request.body; + + // Check if cluster does exist. + const existingCluster = await doesClusterExist(callAsCurrentUser, name); + if (!existingCluster) { + return response.customError({ + statusCode: 404, + body: { + message: i18n.translate( + 'xpack.remoteClusters.updateRemoteCluster.noRemoteClusterErrorMessage', + { + defaultMessage: 'There is no remote cluster with that name.', + } + ), + }, + }); + } + + // Update cluster as new settings + const updateClusterPayload = serializeCluster({ name, seeds, skipUnavailable }); + const updateClusterResponse = await callAsCurrentUser('cluster.putSettings', { + body: updateClusterPayload, + }); + + const acknowledged = get(updateClusterResponse, 'acknowledged'); + const cluster = get(updateClusterResponse, `persistent.cluster.remote.${name}`); + + if (acknowledged && cluster) { + const body = { + ...deserializeCluster(name, cluster), + isConfiguredByNode: false, + }; + return response.ok({ body }); + } + + // If for some reason the ES response did not acknowledge, + // return an error. This shouldn't happen. + return response.customError({ + statusCode: 400, + body: { + message: i18n.translate( + 'xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage', + { + defaultMessage: 'Unable to edit cluster, no response returned from ES.', + } + ), + }, + }); + } catch (error) { + if (isEsError(error)) { + return response.customError({ statusCode: error.statusCode, body: error }); + } + return response.internalError({ body: error }); + } + }; + + deps.router.put( + { + path: `${API_BASE_PATH}/{name}`, + validate: { + params: paramsValidation, + body: bodyValidation, + }, + }, + licensePreRoutingFactory(deps, updateHandler) + ); +}; diff --git a/x-pack/plugins/remote_clusters/server/types.ts b/x-pack/plugins/remote_clusters/server/types.ts new file mode 100644 index 0000000000000..708b1daf4bbad --- /dev/null +++ b/x-pack/plugins/remote_clusters/server/types.ts @@ -0,0 +1,24 @@ +/* + * 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 { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'kibana/server'; +import { LicensingPluginSetup } from '../../licensing/server'; + +export interface Dependencies { + licensing: LicensingPluginSetup; +} + +export interface RouteDependencies { + router: IRouter; + getLicenseStatus: () => LicenseStatus; + elasticsearchService: ElasticsearchServiceSetup; + elasticsearch: IClusterClient; +} + +export interface LicenseStatus { + valid: boolean; + message?: string; +} diff --git a/x-pack/plugins/security/common/licensing/index.ts b/x-pack/plugins/security/common/licensing/index.ts index 9ddbe86167367..e8efae3dc6a6b 100644 --- a/x-pack/plugins/security/common/licensing/index.ts +++ b/x-pack/plugins/security/common/licensing/index.ts @@ -5,3 +5,5 @@ */ export { SecurityLicenseService, SecurityLicense } from './license_service'; + +export { SecurityLicenseFeatures } from './license_features'; diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index 4caf3d25e887b..46bbedd37c434 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -8,7 +8,6 @@ import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { AuthenticatedUser } from '../../common/model'; import { AccountManagementPage } from './account_management_page'; - import { coreMock } from 'src/core/public/mocks'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import { securityMock } from '../mocks'; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index 336ec37d76a1b..712f49afd306e 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -10,6 +10,7 @@ import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plug export { SecurityPluginSetup, SecurityPluginStart }; export { SessionInfo } from './types'; export { AuthenticatedUser } from '../common/model'; +export { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; export const plugin: PluginInitializer = () => new SecurityPlugin(); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index e183eae08d1e1..23a3f327a2c5c 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { Capabilities } from 'src/core/public'; -import { Space } from '../../../../../spaces/common/model/space'; import { Feature } from '../../../../../features/public'; // These modules should be moved into a common directory // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -28,6 +27,7 @@ import { dataPluginMock } from '../../../../../../../src/plugins/data/public/moc import { licenseMock } from '../../../../common/licensing/index.mock'; import { userAPIClientMock } from '../../users/index.mock'; import { rolesAPIClientMock, indicesAPIClientMock, privilegesAPIClientMock } from '../index.mock'; +import { Space } from '../../../../../spaces/public'; const buildFeatures = () => { return [ diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 33e69a68ca896..42ec3fa419167 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -37,7 +37,7 @@ import { NotificationsStart, } from 'src/core/public'; import { IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; -import { Space } from '../../../../../spaces/common/model/space'; +import { Space } from '../../../../../spaces/public'; import { Feature } from '../../../../../features/public'; import { KibanaPrivileges, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx index 4ebe02e687159..a4e287632c764 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx @@ -6,7 +6,7 @@ import React, { Component } from 'react'; import { Capabilities } from 'src/core/public'; -import { Space } from '../../../../../../../spaces/common/model/space'; +import { Space } from '../../../../../../../spaces/public'; import { Feature } from '../../../../../../../features/public'; import { KibanaPrivileges, Role } from '../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from './kibana_privilege_calculator'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx index 16aad4826ae44..a01c026c1a5df 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonEmpty, EuiInMemoryTable } from '@elastic/eui'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space } from '../../../../../../../../spaces/public'; import { Feature } from '../../../../../../../../features/public'; import { KibanaPrivileges, Role } from '../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index b3449e32c6c91..f0f425273e25d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -19,8 +19,7 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { SpaceAvatar } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space, SpaceAvatar } from '../../../../../../../../spaces/public'; import { Feature } from '../../../../../../../../features/public'; import { FeaturesPrivileges, Role } from '../../../../../../../common/model'; import { CalculatedPrivilege } from '../kibana_privilege_calculator'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index 6d1f5117c52e9..6f841b5d14cb3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space } from '../../../../../../../../spaces/public'; import { Feature } from '../../../../../../../../features/public'; import { KibanaPrivileges, Role, copyRole } from '../../../../../../../common/model'; import { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index 1c27ec84f50dc..1a43fb9e2683a 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -13,8 +13,7 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { getSpaceColor } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space, getSpaceColor } from '../../../../../../../../spaces/public'; import { FeaturesPrivileges, Role, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index b2b92356e5126..5fc238eed0ae7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -15,7 +15,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; import { Capabilities } from 'src/core/public'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space } from '../../../../../../../../spaces/public'; import { Feature } from '../../../../../../../../features/public'; import { KibanaPrivileges, Role, isReservedRole } from '../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../kibana_privilege_calculator'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx index cfeb5b9f37d8c..3e5ea9f146876 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -7,8 +7,7 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiHealth, EuiHighlight } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { getSpaceColor } from '../../../../../../../../../legacy/plugins/spaces/public/space_avatar'; -import { Space } from '../../../../../../../../spaces/common/model/space'; +import { Space, getSpaceColor } from '../../../../../../../../spaces/public'; const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => { if (!space) { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx index bb7a6db97f7c8..f8b2991a844f7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx @@ -14,9 +14,8 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { SpaceAvatar } from '../../../../../../../legacy/plugins/spaces/public/space_avatar'; +import { Space, SpaceAvatar } from '../../../../../../spaces/public'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../spaces/common'; -import { Space } from '../../../../../../spaces/common/model/space'; interface Props { spaces: Space[]; diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index 543d20bb92afe..7a7542909431f 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -10,7 +10,6 @@ import { EditUserPage } from './edit_user_page'; import React from 'react'; import { User, Role } from '../../../../common/model'; import { ReactWrapper } from 'enzyme'; - import { coreMock } from '../../../../../../../src/core/public/mocks'; import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; import { securityMock } from '../../../mocks'; diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts index 3c0c59d10abd1..33c1d1446afba 100644 --- a/x-pack/plugins/security/public/mocks.ts +++ b/x-pack/plugins/security/public/mocks.ts @@ -6,11 +6,13 @@ import { authenticationMock } from './authentication/index.mock'; import { createSessionTimeoutMock } from './session/session_timeout.mock'; +import { licenseMock } from '../common/licensing/index.mock'; function createSetupMock() { return { authc: authenticationMock.createSetup(), sessionTimeout: createSessionTimeoutMock(), + license: licenseMock.create(), }; } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 153e7112dc95b..813304148ec77 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -57,7 +57,7 @@ export class SecurityNavControlService { } private registerSecurityNavControl( - core: Pick + core: Pick ) { const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ @@ -65,10 +65,12 @@ export class SecurityNavControlService { mount: (el: HTMLElement) => { const I18nContext = core.i18n.Context; + const logoutUrl = core.injectedMetadata.getInjectedVar('logoutUrl') as string; + const props = { user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/app/kibana#/account'), - logoutUrl: core.http.basePath.prepend(`/logout`), + logoutUrl, }; ReactDOM.render( diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 394e23cbbf646..f1ac2e2ecc3e2 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -107,10 +107,11 @@ export class SecurityPlugin return { authc: this.authc, sessionTimeout: this.sessionTimeout, + license, }; } - public start(core: CoreStart, { data, management }: PluginStartDependencies) { + public start(core: CoreStart, { management }: PluginStartDependencies) { this.sessionTimeout.start(); this.navControlService.start({ core }); diff --git a/x-pack/legacy/plugins/spaces/common/lib/dataurl.ts b/x-pack/plugins/spaces/common/lib/dataurl.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/common/lib/dataurl.ts rename to x-pack/plugins/spaces/common/lib/dataurl.ts diff --git a/x-pack/plugins/spaces/kibana.json b/x-pack/plugins/spaces/kibana.json index d806aaf1807ef..4242d2f5c5d09 100644 --- a/x-pack/plugins/spaces/kibana.json +++ b/x-pack/plugins/spaces/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "spaces"], "requiredPlugins": ["features", "licensing"], - "optionalPlugins": ["security", "home", "usageCollection"], + "optionalPlugins": ["advancedSettings", "home", "management", "security", "usageCollection"], "server": true, - "ui": false + "ui": true } diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx b/x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx similarity index 78% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx rename to x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx index 3c6b2332bbbdc..08cc0a638cd78 100644 --- a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx +++ b/x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx @@ -5,7 +5,7 @@ */ import { AdvancedSettingsService } from './advanced_settings_service'; -import { advancedSettingsMock } from '../../../../../../src/plugins/advanced_settings/public/mocks'; +import { advancedSettingsMock } from '../../../../../src/plugins/advanced_settings/public/mocks'; const componentRegistryMock = advancedSettingsMock.createSetupContract(); @@ -14,7 +14,7 @@ describe('Advanced Settings Service', () => { it('registers space-aware components to augment the advanced settings screen', () => { const deps = { getActiveSpace: jest.fn().mockResolvedValue({ id: 'foo', name: 'foo-space' }), - componentRegistry: componentRegistryMock, + componentRegistry: componentRegistryMock.component, }; const advancedSettingsService = new AdvancedSettingsService(); @@ -22,13 +22,13 @@ describe('Advanced Settings Service', () => { expect(deps.componentRegistry.register).toHaveBeenCalledTimes(2); expect(deps.componentRegistry.register).toHaveBeenCalledWith( - componentRegistryMock.componentType.PAGE_TITLE_COMPONENT, + componentRegistryMock.component.componentType.PAGE_TITLE_COMPONENT, expect.any(Function), true ); expect(deps.componentRegistry.register).toHaveBeenCalledWith( - componentRegistryMock.componentType.PAGE_SUBTITLE_COMPONENT, + componentRegistryMock.component.componentType.PAGE_SUBTITLE_COMPONENT, expect.any(Function), true ); diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx b/x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx similarity index 91% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx rename to x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx index a1552add18f2d..5dedb285014f8 100644 --- a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx +++ b/x-pack/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { AdvancedSettingsSetup } from 'src/plugins/advanced_settings/public'; import { Space } from '../../common/model/space'; import { AdvancedSettingsTitle, AdvancedSettingsSubtitle } from './components'; -import { AdvancedSettingsSetup } from '../../../../../../src/plugins/advanced_settings/public'; interface SetupDeps { getActiveSpace: () => Promise; diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx index b74524db81d81..413be03d08bfc 100644 --- a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx +++ b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState, useEffect } from 'react'; -import { Space } from '../../../../../../../plugins/spaces/common/model/space'; +import { Space } from '../../../../common/model/space'; import { SpaceAvatar } from '../../../space_avatar'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts rename to x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts b/x-pack/plugins/spaces/public/advanced_settings/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts rename to x-pack/plugins/spaces/public/advanced_settings/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts b/x-pack/plugins/spaces/public/advanced_settings/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts rename to x-pack/plugins/spaces/public/advanced_settings/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/constants.ts b/x-pack/plugins/spaces/public/constants.ts similarity index 80% rename from x-pack/legacy/plugins/spaces/public/constants.ts rename to x-pack/plugins/spaces/public/constants.ts index 94799f6f2b5d8..15c65722d01ef 100644 --- a/x-pack/legacy/plugins/spaces/public/constants.ts +++ b/x-pack/plugins/spaces/public/constants.ts @@ -5,7 +5,6 @@ */ import { i18n } from '@kbn/i18n'; -import { npSetup } from 'ui/new_platform'; let spacesFeatureDescription: string; @@ -19,6 +18,3 @@ export const getSpacesFeatureDescription = () => { return spacesFeatureDescription; }; - -export const getManageSpacesUrl = () => - npSetup.core.http.basePath.prepend(`/app/kibana#/management/spaces/list`); diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index 28011911e212e..7809b511adda4 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -17,9 +17,9 @@ import { act } from '@testing-library/react'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { ToastsApi } from 'src/core/public'; -jest.mock('../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ +jest.mock('../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); @@ -86,7 +86,7 @@ const setup = async (opts: SetupOpts = {}) => { ); diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index f486f2f24f13d..cd2f3986600ff 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -21,12 +21,12 @@ import { import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ToastsStart } from 'src/core/public'; import { + SavedObjectsManagementRecord, ProcessedImportResponse, processImportResponse, -} from '../../../../../../../src/legacy/core_plugins/management/public'; +} from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../spaces_manager'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; @@ -38,7 +38,7 @@ interface Props { onClose: () => void; savedObject: SavedObjectsManagementRecord; spacesManager: SpacesManager; - toastNotifications: ToastNotifications; + toastNotifications: ToastsStart; } export const CopySavedObjectsToSpaceFlyout = (props: Props) => { diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index 56f39ce3ed4fb..b22cec0af5ea8 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; import { ImportRetry } from '../types'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index 285abb828a011..2735218fdd370 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,8 +13,10 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { + SavedObjectsManagementRecord, + ProcessedImportResponse, +} from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../common/model/space'; import { CopyOptions, ImportRetry } from '../types'; import { SpaceResult } from './space_result'; diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx index 22f0767ba196e..b667ec2a6e11d 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { SummarizedCopyToSpaceResult } from '../index'; import { SpaceAvatar } from '../../space_avatar'; import { Space } from '../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx index d3ab406b87c3e..cae72bb1e75e9 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { SummarizedCopyToSpaceResult } from '../index'; -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; import { ImportRetry } from '../types'; diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx similarity index 83% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index c016494a4cdf9..c9297ed97f09b 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -5,11 +5,11 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; +import { NotificationsStart } from 'src/core/public'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from '../../../../../../src/legacy/core_plugins/management/public'; +} from '../../../../../src/legacy/core_plugins/management/public'; import { CopySavedObjectsToSpaceFlyout } from './components'; import { SpacesManager } from '../spaces_manager'; @@ -30,7 +30,10 @@ export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagem }, }; - constructor(private readonly spacesManager: SpacesManager) { + constructor( + private readonly spacesManager: SpacesManager, + private readonly notifications: NotificationsStart + ) { super(); } @@ -43,7 +46,7 @@ export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagem onClose={this.onClose} savedObject={this.record} spacesManager={this.spacesManager} - toastNotifications={toastNotifications} + toastNotifications={this.notifications.toasts} /> ); }; diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts similarity index 90% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts index 63a59344dfe5d..2603f80a19520 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts @@ -8,12 +8,14 @@ import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; import { spacesManagerMock } from '../spaces_manager/mocks'; import { CopySavedObjectsToSpaceService } from '.'; +import { notificationServiceMock } from 'src/core/public/mocks'; describe('CopySavedObjectsToSpaceService', () => { describe('#setup', () => { it('registers the CopyToSpaceSavedObjectsManagementAction', () => { const deps = { spacesManager: spacesManagerMock.create(), + notificationsSetup: notificationServiceMock.createSetupContract(), // we don't have a proper NP mock for this yet managementSetup: ({ savedObjects: { diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts similarity index 71% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts index 37354f985a2fc..2db3d4b319acb 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts @@ -5,17 +5,19 @@ */ import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { NotificationsSetup } from 'src/core/public'; import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; import { SpacesManager } from '../spaces_manager'; interface SetupDeps { spacesManager: SpacesManager; - managementSetup: ManagementSetup; + managementSetup: Pick; + notificationsSetup: NotificationsSetup; } export class CopySavedObjectsToSpaceService { - public setup({ spacesManager, managementSetup }: SetupDeps) { - const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager); + public setup({ spacesManager, managementSetup, notificationsSetup }: SetupDeps) { + const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager, notificationsSetup); managementSetup.savedObjects.registry.register(action); } } diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts similarity index 98% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index 0244a35711e6f..fb2616619c644 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from 'src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts similarity index 94% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 7bc47d35efc6c..5ad4fe2cad3ab 100644 --- a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { + ProcessedImportResponse, + SavedObjectsManagementRecord, +} from 'src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/types.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts rename to x-pack/plugins/spaces/public/copy_saved_objects_to_space/types.ts diff --git a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts similarity index 88% rename from x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts rename to x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts index 464066d2221de..2cf34e842ce33 100644 --- a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts +++ b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { FeatureCatalogueEntry, FeatureCatalogueCategory, -} from '../../../../../src/plugins/home/public'; +} from '../../../../src/plugins/home/public'; import { getSpacesFeatureDescription } from './constants'; export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { @@ -19,7 +19,7 @@ export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { }), description: getSpacesFeatureDescription(), icon: 'spacesApp', - path: '/app/kibana#/management/spaces/list', + path: '/app/kibana#/management/kibana/spaces', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }; diff --git a/x-pack/plugins/spaces/public/index.scss b/x-pack/plugins/spaces/public/index.scss new file mode 100644 index 0000000000000..26269f1d31aa3 --- /dev/null +++ b/x-pack/plugins/spaces/public/index.scss @@ -0,0 +1,16 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +/* Spaces plugin styles */ + +// Prefix all styles with "spc" to avoid conflicts. +// Examples +// spcChart +// spcChart__legend +// spcChart__legend--small +// spcChart__legend-isLoading + +@import './management/index'; +@import './nav_control/index'; +@import './space_selector/index'; +@import './copy_saved_objects_to_space/index'; diff --git a/x-pack/legacy/plugins/spaces/public/index.ts b/x-pack/plugins/spaces/public/index.ts similarity index 62% rename from x-pack/legacy/plugins/spaces/public/index.ts rename to x-pack/plugins/spaces/public/index.ts index 53cb906a619d3..9abf05cab2786 100644 --- a/x-pack/legacy/plugins/spaces/public/index.ts +++ b/x-pack/plugins/spaces/public/index.ts @@ -5,7 +5,11 @@ */ import { SpacesPlugin } from './plugin'; -export { SpaceAvatar } from './space_avatar'; +export { Space } from '../common/model/space'; + +export { SpaceAvatar, getSpaceColor, getSpaceImageUrl, getSpaceInitials } from './space_avatar'; + +export { SpacesPluginSetup, SpacesPluginStart } from './plugin'; export const plugin = () => { return new SpacesPlugin(); diff --git a/x-pack/legacy/plugins/spaces/public/management/_index.scss b/x-pack/plugins/spaces/public/management/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/_index.scss rename to x-pack/plugins/spaces/public/management/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap rename to x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss rename to x-pack/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx rename to x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx rename to x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts rename to x-pack/plugins/spaces/public/management/components/confirm_delete_modal/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/components/index.ts b/x-pack/plugins/spaces/public/management/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/index.ts rename to x-pack/plugins/spaces/public/management/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts b/x-pack/plugins/spaces/public/management/components/secure_space_message/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts rename to x-pack/plugins/spaces/public/management/components/secure_space_message/index.ts diff --git a/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx new file mode 100644 index 0000000000000..38d8451b658a8 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx @@ -0,0 +1,40 @@ +/* + * 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 { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; + +export const SecureSpaceMessage = () => { + const rolesLinkTextAriaLabel = i18n.translate( + 'xpack.spaces.management.secureSpaceMessage.rolesLinkTextAriaLabel', + { defaultMessage: 'Roles management page' } + ); + return ( + + + +

+ + + + ), + }} + /> +

+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap rename to x-pack/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts rename to x-pack/plugins/spaces/public/management/components/unauthorized_prompt/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx rename to x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx rename to x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap rename to x-pack/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap rename to x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx rename to x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts rename to x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx similarity index 77% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx index b0d74afaa90aa..d581ac22650e3 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx @@ -16,7 +16,8 @@ import { EuiTextArea, EuiTitle, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import React, { ChangeEvent, Component, Fragment } from 'react'; import { isReservedSpace } from '../../../../common'; import { Space } from '../../../../common/model/space'; @@ -30,7 +31,6 @@ interface Props { validator: SpaceValidator; space: Partial; editingExistingSpace: boolean; - intl: InjectedIntl; onChange: (space: Partial) => void; } @@ -46,24 +46,21 @@ export class CustomizeSpace extends Component { }; public render() { - const { validator, editingExistingSpace, intl } = this.props; + const { validator, editingExistingSpace } = this.props; const { name = '', description = '' } = this.props.space; - const panelTitle = intl.formatMessage({ - id: 'xpack.spaces.management.manageSpacePage.customizeSpaceTitle', - defaultMessage: 'Customize your space', - }); + const panelTitle = i18n.translate( + 'xpack.spaces.management.manageSpacePage.customizeSpaceTitle', + { + defaultMessage: 'Customize your space', + } + ); const extraPopoverProps: Partial = { initialFocus: 'input[name="spaceInitials"]', }; return ( - + @@ -81,8 +78,7 @@ export class CustomizeSpace extends Component { { > { @@ -147,14 +149,18 @@ export class CustomizeSpace extends Component { )} diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx similarity index 91% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx index 642f2f0013222..dc20a375ada1e 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx @@ -15,9 +15,7 @@ const space = { }; test('renders without crashing', () => { - const wrapper = shallowWithIntl( - - ); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx similarity index 83% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx index c3207c82bf95e..55fea3671645b 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx @@ -16,16 +16,14 @@ import { EuiSpacer, isValidHex, } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { Space } from '../../../../common/model/space'; import { imageTypes, encode } from '../../../../common/lib/dataurl'; import { getSpaceColor, getSpaceInitials } from '../../../space_avatar'; -import { Space } from '../../../../../../../plugins/spaces/common/model/space'; -import { MAX_SPACE_INITIALS } from '../../../../../../../plugins/spaces/common'; - +import { MAX_SPACE_INITIALS } from '../../../../common'; interface Props { space: Partial; onChange: (space: Partial) => void; - intl: InjectedIntl; } interface State { @@ -33,7 +31,7 @@ interface State { pendingInitials?: string | null; } -class CustomizeSpaceAvatarUI extends Component { +export class CustomizeSpaceAvatar extends Component { private initialsRef: HTMLInputElement | null = null; constructor(props: Props) { @@ -101,7 +99,7 @@ class CustomizeSpaceAvatarUI extends Component { }; public render() { - const { space, intl } = this.props; + const { space } = this.props; const { initialsHasFocus, pendingInitials } = this.state; @@ -111,10 +109,12 @@ class CustomizeSpaceAvatarUI extends Component { return (
false}> { { } public filePickerOrImage() { - const { intl } = this.props; - if (!this.props.space.imageUrl) { return ( @@ -179,8 +177,7 @@ class CustomizeSpaceAvatarUI extends Component { return ( this.removeImageUrl()} color="danger" iconType="trash"> - {intl.formatMessage({ - id: 'xpack.spaces.management.customizeSpaceAvatar.removeImage', + {i18n.translate('xpack.spaces.management.customizeSpaceAvatar.removeImage', { defaultMessage: 'Remove custom image', })} @@ -237,5 +234,3 @@ class CustomizeSpaceAvatarUI extends Component { }); }; } - -export const CustomizeSpaceAvatar = injectI18n(CustomizeSpaceAvatarUI); diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts b/x-pack/plugins/spaces/public/management/edit_space/customize_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx rename to x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx similarity index 82% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx index 364145d6495b8..fbb2a3d07a293 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx @@ -9,6 +9,7 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DeleteSpacesButton } from './delete_spaces_button'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; +import { notificationServiceMock } from 'src/core/public/mocks'; const space = { id: 'my-space', @@ -20,12 +21,14 @@ describe('DeleteSpacesButton', () => { it('renders as expected', () => { const spacesManager = spacesManagerMock.create(); + const notifications = notificationServiceMock.createStartContract(); + const wrapper = shallowWithIntl( - ); expect(wrapper).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx similarity index 69% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx rename to x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx index 56a858eb4ccf6..28e45bc8cfd2a 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx @@ -5,9 +5,10 @@ */ import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { toastNotifications } from 'ui/notify'; +import { NotificationsStart } from 'src/core/public'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../spaces_manager'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; @@ -17,7 +18,7 @@ interface Props { space: Space; spacesManager: SpacesManager; onDelete: () => void; - intl: InjectedIntl; + notifications: NotificationsStart; } interface State { @@ -25,7 +26,7 @@ interface State { showConfirmRedirectModal: boolean; } -class DeleteSpacesButtonUI extends Component { +export class DeleteSpacesButton extends Component { public state = { showConfirmDeleteModal: false, showConfirmRedirectModal: false, @@ -38,7 +39,6 @@ class DeleteSpacesButtonUI extends Component { defaultMessage="Delete space" /> ); - const { intl } = this.props; let ButtonComponent: any = EuiButton; @@ -54,10 +54,12 @@ class DeleteSpacesButtonUI extends Component { {buttonText} @@ -95,23 +97,18 @@ class DeleteSpacesButtonUI extends Component { }; public deleteSpaces = async () => { - const { spacesManager, space, intl } = this.props; + const { spacesManager, space } = this.props; try { await spacesManager.deleteSpace(space); } catch (error) { const { message: errorMessage = '' } = error.data || {}; - toastNotifications.addDanger( - intl.formatMessage( - { - id: 'xpack.spaces.management.deleteSpacesButton.deleteSpaceErrorTitle', - defaultMessage: 'Error deleting space: {errorMessage}', - }, - { - errorMessage, - } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.spaces.management.deleteSpacesButton.deleteSpaceErrorTitle', { + defaultMessage: 'Error deleting space: {errorMessage}', + values: { errorMessage }, + }) ); } @@ -119,23 +116,18 @@ class DeleteSpacesButtonUI extends Component { showConfirmDeleteModal: false, }); - const message = intl.formatMessage( + const message = i18n.translate( + 'xpack.spaces.management.deleteSpacesButton.spaceSuccessfullyDeletedNotificationMessage', { - id: - 'xpack.spaces.management.deleteSpacesButton.spaceSuccessfullyDeletedNotificationMessage', defaultMessage: 'Deleted {spaceName} space.', - }, - { - spaceName: space.name, + values: { spaceName: space.name }, } ); - toastNotifications.addSuccess(message); + this.props.notifications.toasts.addSuccess(message); if (this.props.onDelete) { this.props.onDelete(); } }; } - -export const DeleteSpacesButton = injectI18n(DeleteSpacesButtonUI); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap new file mode 100644 index 0000000000000..7db3d5456fbd3 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap @@ -0,0 +1,120 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EnabledFeatures renders as expected 1`] = ` + + + + + + + + + + } +> + + + +

+ +

+
+ + +

+ +

+

+ + + , + } + } + /> +

+
+
+ + + +
+
+`; diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss rename to x-pack/plugins/spaces/public/management/edit_space/enabled_features/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx similarity index 88% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx index f770857d9313d..d9282ad0457dd 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx @@ -7,10 +7,10 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; import { SectionPanel } from '../section_panel'; import { EnabledFeatures } from './enabled_features'; +import { Feature } from '../../../../../features/public'; const features: Feature[] = [ { @@ -35,15 +35,6 @@ const space: Space = { disabledFeatures: ['feature-1', 'feature-2'], }; -const capabilities = { - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { - manage: true, - }, -}; - describe('EnabledFeatures', () => { it(`renders as expected`, () => { expect( @@ -51,8 +42,7 @@ describe('EnabledFeatures', () => { ) @@ -66,8 +56,7 @@ describe('EnabledFeatures', () => { ); @@ -101,8 +90,7 @@ describe('EnabledFeatures', () => { ); diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx rename to x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 70312296f757b..52a0fe8d4d26c 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -5,10 +5,10 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; -import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../plugins/features/public'; +import { Feature } from '../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; @@ -17,17 +17,18 @@ import { FeatureTable } from './feature_table'; interface Props { space: Partial; features: Feature[]; - capabilities: Capabilities; - intl: InjectedIntl; + securityEnabled: boolean; onChange: (space: Partial) => void; } export class EnabledFeatures extends Component { public render() { - const description = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.manageSpacePage.customizeVisibleFeatures', - defaultMessage: 'Customize visible features', - }); + const description = i18n.translate( + 'xpack.spaces.management.manageSpacePage.customizeVisibleFeatures', + { + defaultMessage: 'Customize visible features', + } + ); return ( { initiallyCollapsed title={this.getPanelTitle()} description={description} - intl={this.props.intl} data-test-subj="enabled-features-panel" > @@ -56,7 +56,6 @@ export class EnabledFeatures extends Component { features={this.props.features} space={this.props.space} onChange={this.props.onChange} - intl={this.props.intl} />
@@ -130,7 +129,7 @@ export class EnabledFeatures extends Component { defaultMessage="The feature is hidden in the UI, but is not disabled." />

- {this.props.capabilities.spaces.manage && ( + {this.props.securityEnabled && (

; features: Feature[]; - intl: InjectedIntl; onChange: (space: Partial) => void; } @@ -66,8 +66,7 @@ export class FeatureTable extends Component { private getColumns = () => [ { field: 'feature', - name: this.props.intl.formatMessage({ - id: 'xpack.spaces.management.enabledSpaceFeaturesFeatureColumnTitle', + name: i18n.translate('xpack.spaces.management.enabledSpaceFeaturesFeatureColumnTitle', { defaultMessage: 'Feature', }), render: (feature: Feature, _item: { feature: Feature; space: Props['space'] }) => { diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts rename to x-pack/plugins/spaces/public/management/edit_space/enabled_features/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx rename to x-pack/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts b/x-pack/plugins/spaces/public/management/edit_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts rename to x-pack/plugins/spaces/public/management/edit_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx similarity index 84% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index d24e932bce112..2aba1522a7e3f 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -3,10 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/kfetch', () => ({ - kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]), -})); -import '../../__mocks__/xpack_info'; + import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; @@ -16,6 +13,7 @@ import { ManageSpacePage } from './manage_space_page'; import { SectionPanel } from './section_panel'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; +import { httpServiceMock, notificationServiceMock } from 'src/core/public/mocks'; const space = { id: 'my-space', @@ -29,10 +27,15 @@ describe('ManageSpacePage', () => { spacesManager.createSpace = jest.fn(spacesManager.createSpace); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]); + const wrapper = mountWithIntl( - { }); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]); + const onLoadSpace = jest.fn(); const wrapper = mountWithIntl( - { }); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]); + const wrapper = mountWithIntl( - { }); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]); + const wrapper = mountWithIntl( - void; capabilities: Capabilities; + securityEnabled: boolean; } interface State { @@ -54,7 +55,7 @@ interface State { }; } -class ManageSpacePageUI extends Component { +export class ManageSpacePage extends Component { private readonly validator: SpaceValidator; constructor(props: Props) { @@ -74,47 +75,24 @@ class ManageSpacePageUI extends Component { return; } - const { spaceId, spacesManager, intl, onLoadSpace } = this.props; + const { spaceId, http } = this.props; - const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); + const getFeatures = http.get('/api/features'); if (spaceId) { - try { - const [space, features] = await Promise.all([spacesManager.getSpace(spaceId), getFeatures]); - if (space) { - if (onLoadSpace) { - onLoadSpace(space); - } - - this.setState({ - space, - features: await features, - originalSpace: space, - isLoading: false, - }); - } - } catch (error) { - const { message = '' } = error.data || {}; - - toastNotifications.addDanger( - intl.formatMessage( - { - id: 'xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle', - defaultMessage: 'Error loading space: {message}', - }, - { - message, - } - ) - ); - this.backToSpacesList(); - } + await this.loadSpace(spaceId, getFeatures); } else { const features = await getFeatures; this.setState({ isLoading: false, features }); } } + public async componentDidUpdate(previousProps: Props) { + if (this.props.spaceId !== previousProps.spaceId && this.props.spaceId) { + await this.loadSpace(this.props.spaceId, Promise.resolve(this.state.features)); + } + } + public render() { const content = this.state.isLoading ? this.getLoadingIndicator() : this.getForm(); @@ -162,7 +140,6 @@ class ManageSpacePageUI extends Component { onChange={this.onSpaceChange} editingExistingSpace={this.editingExistingSpace()} validator={this.validator} - intl={this.props.intl} /> @@ -170,9 +147,8 @@ class ManageSpacePageUI extends Component { @@ -212,27 +188,33 @@ class ManageSpacePageUI extends Component { }; public maybeGetSecureSpacesMessage = () => { - if (this.editingExistingSpace()) { + if (this.editingExistingSpace() && this.props.securityEnabled) { return ; } return null; }; public getFormButtons = () => { - const createSpaceText = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.manageSpacePage.createSpaceButton', - defaultMessage: 'Create space', - }); + const createSpaceText = i18n.translate( + 'xpack.spaces.management.manageSpacePage.createSpaceButton', + { + defaultMessage: 'Create space', + } + ); - const updateSpaceText = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.manageSpacePage.updateSpaceButton', - defaultMessage: 'Update space', - }); + const updateSpaceText = i18n.translate( + 'xpack.spaces.management.manageSpacePage.updateSpaceButton', + { + defaultMessage: 'Update space', + } + ); - const cancelButtonText = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.manageSpacePage.cancelSpaceButton', - defaultMessage: 'Cancel', - }); + const cancelButtonText = i18n.translate( + 'xpack.spaces.management.manageSpacePage.cancelSpaceButton', + { + defaultMessage: 'Cancel', + } + ); const saveText = this.editingExistingSpace() ? updateSpaceText : createSpaceText; return ( @@ -267,6 +249,7 @@ class ManageSpacePageUI extends Component { space={this.state.space as Space} spacesManager={this.props.spacesManager} onDelete={this.backToSpacesList} + notifications={this.props.notifications} /> ); @@ -320,8 +303,40 @@ class ManageSpacePageUI extends Component { } }; + private loadSpace = async (spaceId: string, featuresPromise: Promise) => { + const { spacesManager, onLoadSpace } = this.props; + + try { + const [space, features] = await Promise.all([ + spacesManager.getSpace(spaceId), + featuresPromise, + ]); + if (space) { + if (onLoadSpace) { + onLoadSpace(space); + } + + this.setState({ + space, + features: await features, + originalSpace: space, + isLoading: false, + }); + } + } catch (error) { + const message = error?.body?.message ?? ''; + + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle', { + defaultMessage: 'Error loading space: {message}', + values: { message }, + }) + ); + this.backToSpacesList(); + } + }; + private performSave = (requireRefresh = false) => { - const { intl } = this.props; if (!this.state.space) { return; } @@ -357,19 +372,16 @@ class ManageSpacePageUI extends Component { action .then(() => { - toastNotifications.addSuccess( - intl.formatMessage( + this.props.notifications.toasts.addSuccess( + i18n.translate( + 'xpack.spaces.management.manageSpacePage.spaceSuccessfullySavedNotificationMessage', { - id: - 'xpack.spaces.management.manageSpacePage.spaceSuccessfullySavedNotificationMessage', defaultMessage: `Space {name} was saved.`, - }, - { - name: `'${name}'`, + values: { name: `'${name}'` }, } ) ); - window.location.hash = `#/management/spaces/list`; + window.location.hash = `#/management/kibana/spaces`; if (requireRefresh) { setTimeout(() => { window.location.reload(); @@ -377,29 +389,22 @@ class ManageSpacePageUI extends Component { } }) .catch(error => { - const { message = '' } = error.data || {}; + const message = error?.body?.message ?? ''; this.setState({ saveInProgress: false }); - toastNotifications.addDanger( - intl.formatMessage( - { - id: 'xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle', - defaultMessage: 'Error saving space: {message}', - }, - { - message, - } - ) + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle', { + defaultMessage: 'Error saving space: {message}', + values: { message }, + }) ); }); }; private backToSpacesList = () => { - window.location.hash = `#/management/spaces/list`; + window.location.hash = `#/management/kibana/spaces`; }; private editingExistingSpace = () => !!this.props.spaceId; } - -export const ManageSpacePage = injectI18n(ManageSpacePageUI); diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx rename to x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap rename to x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss b/x-pack/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss rename to x-pack/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts b/x-pack/plugins/spaces/public/management/edit_space/section_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts rename to x-pack/plugins/spaces/public/management/edit_space/section_panel/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx similarity index 73% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx rename to x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx index 9b736c98d1f0c..0b8085ff1ad16 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx @@ -11,13 +11,7 @@ import { SectionPanel } from './section_panel'; test('it renders without blowing up', () => { const wrapper = shallowWithIntl( - +

child

); @@ -27,13 +21,7 @@ test('it renders without blowing up', () => { test('it renders children by default', () => { const wrapper = mountWithIntl( - +

child 1

child 2

@@ -45,13 +33,7 @@ test('it renders children by default', () => { test('it hides children when the "hide" link is clicked', () => { const wrapper = mountWithIntl( - +

child 1

child 2

diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx similarity index 77% rename from x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx rename to x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx index a205130d2d765..a0a6bc5a89d33 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx @@ -14,7 +14,7 @@ import { EuiTitle, IconType, } from '@elastic/eui'; -import { InjectedIntl } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import React, { Component, Fragment, ReactNode } from 'react'; interface Props { @@ -22,7 +22,6 @@ interface Props { title: string | ReactNode; description: string; collapsible: boolean; - intl: InjectedIntl; initiallyCollapsed?: boolean; } @@ -52,38 +51,31 @@ export class SectionPanel extends Component { } public getTitle = () => { - const showLinkText = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.collapsiblePanel.showLinkText', + const showLinkText = i18n.translate('xpack.spaces.management.collapsiblePanel.showLinkText', { defaultMessage: 'show', }); - const hideLinkText = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.collapsiblePanel.hideLinkText', + const hideLinkText = i18n.translate('xpack.spaces.management.collapsiblePanel.hideLinkText', { defaultMessage: 'hide', }); - const showLinkDescription = this.props.intl.formatMessage( + const showLinkDescription = i18n.translate( + 'xpack.spaces.management.collapsiblePanel.showLinkDescription', { - id: 'xpack.spaces.management.collapsiblePanel.showLinkDescription', defaultMessage: 'show {title}', - }, - { - title: this.props.description, + values: { title: this.props.description }, } ); - const hideLinkDescription = this.props.intl.formatMessage( + const hideLinkDescription = i18n.translate( + 'xpack.spaces.management.collapsiblePanel.hideLinkDescription', { - id: 'xpack.spaces.management.collapsiblePanel.hideLinkDescription', defaultMessage: 'hide {title}', - }, - { - title: this.props.description, + values: { title: this.props.description }, } ); return ( - // @ts-ignore diff --git a/x-pack/legacy/plugins/spaces/public/management/index.ts b/x-pack/plugins/spaces/public/management/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/index.ts rename to x-pack/plugins/spaces/public/management/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts b/x-pack/plugins/spaces/public/management/lib/feature_utils.test.ts similarity index 95% rename from x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts rename to x-pack/plugins/spaces/public/management/lib/feature_utils.test.ts index ce874956d0ef2..a3360969fb3f2 100644 --- a/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts +++ b/x-pack/plugins/spaces/public/management/lib/feature_utils.test.ts @@ -5,7 +5,7 @@ */ import { getEnabledFeatures } from './feature_utils'; -import { Feature } from '../../../../../../plugins/features/public'; +import { Feature } from '../../../../features/public'; const buildFeatures = () => [ diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts b/x-pack/plugins/spaces/public/management/lib/feature_utils.ts similarity index 74% rename from x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts rename to x-pack/plugins/spaces/public/management/lib/feature_utils.ts index ff1688637ef73..a1b64eb954403 100644 --- a/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts +++ b/x-pack/plugins/spaces/public/management/lib/feature_utils.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../plugins/features/common'; +import { Feature } from '../../../../features/common'; -import { Space } from '../../../../../../plugins/spaces/common/model/space'; +import { Space } from '../..'; export function getEnabledFeatures(features: Feature[], space: Partial) { return features.filter(feature => !(space.disabledFeatures || []).includes(feature.id)); diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/index.ts b/x-pack/plugins/spaces/public/management/lib/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/lib/index.ts rename to x-pack/plugins/spaces/public/management/lib/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts b/x-pack/plugins/spaces/public/management/lib/space_identifier_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts rename to x-pack/plugins/spaces/public/management/lib/space_identifier_utils.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts b/x-pack/plugins/spaces/public/management/lib/space_identifier_utils.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts rename to x-pack/plugins/spaces/public/management/lib/space_identifier_utils.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts b/x-pack/plugins/spaces/public/management/lib/validate_space.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts rename to x-pack/plugins/spaces/public/management/lib/validate_space.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts b/x-pack/plugins/spaces/public/management/lib/validate_space.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts rename to x-pack/plugins/spaces/public/management/lib/validate_space.ts diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts new file mode 100644 index 0000000000000..d4c6bdaea2776 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/management_service.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { ManagementService } from '.'; +import { coreMock } from 'src/core/public/mocks'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { managementPluginMock } from '../../../../../src/plugins/management/public/mocks'; +import { ManagementSection } from 'src/plugins/management/public'; +import { Capabilities } from 'kibana/public'; + +describe('ManagementService', () => { + describe('#setup', () => { + it('registers the spaces management page under the kibana section', () => { + const mockKibanaSection = ({ + registerApp: jest.fn(), + } as unknown) as ManagementSection; + const deps = { + management: managementPluginMock.createSetupContract(), + getStartServices: coreMock.createSetup().getStartServices, + spacesManager: spacesManagerMock.create(), + }; + + deps.management.sections.getSection.mockReturnValue(mockKibanaSection); + + const service = new ManagementService(); + service.setup(deps); + + expect(deps.management.sections.getSection).toHaveBeenCalledTimes(1); + expect(deps.management.sections.getSection).toHaveBeenCalledWith('kibana'); + + expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.registerApp).toHaveBeenCalledWith({ + id: 'spaces', + title: 'Spaces', + order: 10, + mount: expect.any(Function), + }); + }); + + it('will not crash if the kibana section is missing', () => { + const deps = { + management: managementPluginMock.createSetupContract(), + getStartServices: coreMock.createSetup().getStartServices, + spacesManager: spacesManagerMock.create(), + }; + + const service = new ManagementService(); + service.setup(deps); + }); + }); + + describe('#start', () => { + it('disables the spaces management page if the user is not authorized', () => { + const mockSpacesManagementPage = { disable: jest.fn() }; + const mockKibanaSection = ({ + registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage), + } as unknown) as ManagementSection; + + const deps = { + management: managementPluginMock.createSetupContract(), + getStartServices: coreMock.createSetup().getStartServices, + spacesManager: spacesManagerMock.create(), + }; + + deps.management.sections.getSection.mockImplementation(id => { + if (id === 'kibana') return mockKibanaSection; + throw new Error(`unexpected getSection call: ${id}`); + }); + + const service = new ManagementService(); + service.setup(deps); + + const capabilities = ({ spaces: { manage: false } } as unknown) as Capabilities; + service.start({ capabilities }); + + expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1); + expect(mockSpacesManagementPage.disable).toHaveBeenCalledTimes(1); + }); + + it('does not disable the spaces management page if the user is authorized', () => { + const mockSpacesManagementPage = { disable: jest.fn() }; + const mockKibanaSection = ({ + registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage), + } as unknown) as ManagementSection; + + const deps = { + management: managementPluginMock.createSetupContract(), + getStartServices: coreMock.createSetup().getStartServices, + spacesManager: spacesManagerMock.create(), + }; + + deps.management.sections.getSection.mockImplementation(id => { + if (id === 'kibana') return mockKibanaSection; + throw new Error(`unexpected getSection call: ${id}`); + }); + + const service = new ManagementService(); + service.setup(deps); + + const capabilities = ({ spaces: { manage: true } } as unknown) as Capabilities; + service.start({ capabilities }); + + expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1); + expect(mockSpacesManagementPage.disable).toHaveBeenCalledTimes(0); + }); + }); + + describe('#stop', () => { + it('disables the spaces management page', () => { + const mockSpacesManagementPage = { disable: jest.fn() }; + const mockKibanaSection = ({ + registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage), + } as unknown) as ManagementSection; + + const deps = { + management: managementPluginMock.createSetupContract(), + getStartServices: coreMock.createSetup().getStartServices, + spacesManager: spacesManagerMock.create(), + }; + + deps.management.sections.getSection.mockImplementation(id => { + if (id === 'kibana') return mockKibanaSection; + throw new Error(`unexpected getSection call: ${id}`); + }); + + const service = new ManagementService(); + service.setup(deps); + + service.stop(); + + expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1); + expect(mockSpacesManagementPage.disable).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/spaces/public/management/management_service.tsx b/x-pack/plugins/spaces/public/management/management_service.tsx new file mode 100644 index 0000000000000..c81a3497762a5 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/management_service.tsx @@ -0,0 +1,51 @@ +/* + * 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 { ManagementSetup, ManagementApp } from 'src/plugins/management/public'; +import { CoreSetup, Capabilities } from 'src/core/public'; +import { SecurityLicense } from '../../../security/public'; +import { SpacesManager } from '../spaces_manager'; +import { PluginsStart } from '../plugin'; +import { spacesManagementApp } from './spaces_management_app'; + +interface SetupDeps { + management: ManagementSetup; + getStartServices: CoreSetup['getStartServices']; + spacesManager: SpacesManager; + securityLicense?: SecurityLicense; +} + +interface StartDeps { + capabilities: Capabilities; +} +export class ManagementService { + private registeredSpacesManagementApp?: ManagementApp; + + public setup({ getStartServices, management, spacesManager, securityLicense }: SetupDeps) { + const kibanaSection = management.sections.getSection('kibana'); + if (kibanaSection) { + this.registeredSpacesManagementApp = kibanaSection.registerApp( + spacesManagementApp.create({ getStartServices, spacesManager, securityLicense }) + ); + } + } + + public start({ capabilities }: StartDeps) { + if (!capabilities.spaces.manage) { + this.disableSpacesApp(); + } + } + + public stop() { + this.disableSpacesApp(); + } + + private disableSpacesApp() { + if (this.registeredSpacesManagementApp) { + this.registeredSpacesManagementApp.disable(); + } + } +} diff --git a/x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap similarity index 99% rename from x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap rename to x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap index 02dbca28c7b66..aa6db7e22fd5d 100644 --- a/x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap @@ -39,7 +39,7 @@ exports[`SpacesGridPage renders as expected 1`] = ` > { +export class SpacesGridPage extends Component { constructor(props: Props) { super(props); this.state = { @@ -72,15 +73,13 @@ class SpacesGridPageUI extends Component { return (
{this.getPageContent()} - + {this.props.securityEnabled && } {this.getConfirmDeleteModal()}
); } public getPageContent() { - const { intl } = this.props; - if (!this.props.capabilities.spaces.manage) { return ; } @@ -114,10 +113,12 @@ class SpacesGridPageUI extends Component { sorting={true} search={{ box: { - placeholder: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.searchPlaceholder', - defaultMessage: 'Search', - }), + placeholder: i18n.translate( + 'xpack.spaces.management.spacesGridPage.searchPlaceholder', + { + defaultMessage: 'Search', + } + ), }, }} loading={this.state.loading} @@ -138,12 +139,7 @@ class SpacesGridPageUI extends Component { public getPrimaryActionButton() { return ( - { - window.location.hash = `#/management/spaces/create`; - }} - > + { }; public deleteSpace = async () => { - const { intl } = this.props; const { spacesManager } = this.props; const space = this.state.selectedSpace; @@ -188,16 +183,13 @@ class SpacesGridPageUI extends Component { } catch (error) { const { message: errorMessage = '' } = error.data || {}; - toastNotifications.addDanger( - intl.formatMessage( - { - id: 'xpack.spaces.management.spacesGridPage.errorDeletingSpaceErrorMessage', - defaultMessage: 'Error deleting space: {errorMessage}', - }, - { + this.props.notifications.toasts.addDanger( + i18n.translate('xpack.spaces.management.spacesGridPage.errorDeletingSpaceErrorMessage', { + defaultMessage: 'Error deleting space: {errorMessage}', + values: { errorMessage, - } - ) + }, + }) ); } @@ -207,21 +199,19 @@ class SpacesGridPageUI extends Component { this.loadGrid(); - const message = intl.formatMessage( + const message = i18n.translate( + 'xpack.spaces.management.spacesGridPage.spaceSuccessfullyDeletedNotificationMessage', { - id: 'xpack.spaces.management.spacesGridPage.spaceSuccessfullyDeletedNotificationMessage', defaultMessage: 'Deleted "{spaceName}" space.', - }, - { - spaceName: space.name, + values: { spaceName: space.name }, } ); - toastNotifications.addSuccess(message); + this.props.notifications.toasts.addSuccess(message); }; public loadGrid = async () => { - const { spacesManager } = this.props; + const { spacesManager, http } = this.props; this.setState({ loading: true, @@ -230,7 +220,7 @@ class SpacesGridPageUI extends Component { }); const getSpaces = spacesManager.getSpaces(); - const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); + const getFeatures = http.get('/api/features'); try { const [spaces, features] = await Promise.all([getSpaces, getFeatures]); @@ -248,51 +238,37 @@ class SpacesGridPageUI extends Component { }; public getColumnConfig() { - const { intl } = this.props; return [ { field: 'initials', name: '', width: '50px', render: (value: string, record: Space) => ( - { - this.onEditSpaceClick(record); - }} - > + ), }, { field: 'name', - name: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.spaceColumnName', + name: i18n.translate('xpack.spaces.management.spacesGridPage.spaceColumnName', { defaultMessage: 'Space', }), sortable: true, render: (value: string, record: Space) => ( - { - this.onEditSpaceClick(record); - }} - > - {value} - + {value} ), }, { field: 'description', - name: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.descriptionColumnName', + name: i18n.translate('xpack.spaces.management.spacesGridPage.descriptionColumnName', { defaultMessage: 'Description', }), sortable: true, }, { field: 'disabledFeatures', - name: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.featuresColumnName', + name: i18n.translate('xpack.spaces.management.spacesGridPage.featuresColumnName', { defaultMessage: 'Features', }), sortable: (space: Space) => { @@ -332,8 +308,7 @@ class SpacesGridPageUI extends Component { }, { field: 'id', - name: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.identifierColumnName', + name: i18n.translate('xpack.spaces.management.spacesGridPage.identifierColumnName', { defaultMessage: 'Identifier', }), sortable: true, @@ -345,26 +320,23 @@ class SpacesGridPageUI extends Component { }, }, { - name: intl.formatMessage({ - id: 'xpack.spaces.management.spacesGridPage.actionsColumnName', + name: i18n.translate('xpack.spaces.management.spacesGridPage.actionsColumnName', { defaultMessage: 'Actions', }), actions: [ { render: (record: Space) => ( this.onEditSpaceClick(record)} + href={this.getEditSpacePath(record)} /> ), }, @@ -372,13 +344,11 @@ class SpacesGridPageUI extends Component { available: (record: Space) => !isReservedSpace(record), render: (record: Space) => ( { ]; } - private onEditSpaceClick = (space: Space) => { - window.location.hash = `#/management/spaces/edit/${encodeURIComponent(space.id)}`; + private getEditSpacePath = (space: Space) => { + return `#/management/kibana/spaces/edit/${encodeURIComponent(space.id)}`; }; private onDeleteSpaceClick = (space: Space) => { @@ -403,5 +373,3 @@ class SpacesGridPageUI extends Component { }); }; } - -export const SpacesGridPage = injectI18n(SpacesGridPageUI); diff --git a/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx similarity index 71% rename from x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx rename to x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index 7856d2e7bee01..90c7aba65e3d6 100644 --- a/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -3,16 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/kfetch', () => ({ - kfetch: () => Promise.resolve([]), -})); -import '../../__mocks__/xpack_info'; + import React from 'react'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, shallowWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { SpaceAvatar } from '../../space_avatar'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { SpacesGridPage } from './spaces_grid_page'; +import { httpServiceMock } from 'src/core/public/mocks'; +import { notificationServiceMock } from 'src/core/public/mocks'; const spaces = [ { @@ -41,11 +40,16 @@ spacesManager.getSpaces = jest.fn().mockResolvedValue(spaces); describe('SpacesGridPage', () => { it('renders as expected', () => { + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([]); + expect( shallowWithIntl( - { }); it('renders the list of spaces', async () => { + const httpStart = httpServiceMock.createStartContract(); + httpStart.get.mockResolvedValue([]); + const wrapper = mountWithIntl( - { ); // allow spacesManager to load spaces - await Promise.resolve(); - wrapper.update(); - await Promise.resolve(); + await nextTick(); wrapper.update(); expect(wrapper.find(SpaceAvatar)).toHaveLength(spaces.length); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx new file mode 100644 index 0000000000000..b19ef995283da --- /dev/null +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -0,0 +1,137 @@ +/* + * 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. + */ + +jest.mock('./spaces_grid', () => ({ + SpacesGridPage: (props: any) => `Spaces Page: ${JSON.stringify(props)}`, +})); + +jest.mock('./edit_space', () => ({ + ManageSpacePage: (props: any) => { + if (props.spacesManager && props.onLoadSpace) { + props.spacesManager.getSpace().then((space: any) => props.onLoadSpace(space)); + } + return `Spaces Edit Page: ${JSON.stringify(props)}`; + }, +})); + +import { spacesManagementApp } from './spaces_management_app'; + +import { coreMock } from '../../../../../src/core/public/mocks'; +import { securityMock } from '../../../security/public/mocks'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { SecurityLicenseFeatures } from '../../../security/public'; + +async function mountApp(basePath: string, spaceId?: string) { + const container = document.createElement('div'); + const setBreadcrumbs = jest.fn(); + + const spacesManager = spacesManagerMock.create(); + if (spaceId) { + spacesManager.getSpace.mockResolvedValue({ + id: spaceId, + name: `space with id ${spaceId}`, + disabledFeatures: [], + }); + } + + const securityLicense = securityMock.createSetup().license; + securityLicense.getFeatures.mockReturnValue({ + showLinks: true, + } as SecurityLicenseFeatures); + + const unmount = await spacesManagementApp + .create({ + spacesManager, + securityLicense, + getStartServices: coreMock.createSetup().getStartServices as any, + }) + .mount({ basePath, element: container, setBreadcrumbs }); + + return { unmount, container, setBreadcrumbs }; +} + +describe('spacesManagementApp', () => { + it('create() returns proper management app descriptor', () => { + expect( + spacesManagementApp.create({ + spacesManager: spacesManagerMock.create(), + securityLicense: securityMock.createSetup().license, + getStartServices: coreMock.createSetup().getStartServices as any, + }) + ).toMatchInlineSnapshot(` + Object { + "id": "spaces", + "mount": [Function], + "order": 10, + "title": "Spaces", + } + `); + }); + + it('mount() works for the `grid` page', async () => { + const basePath = '/some-base-path/spaces'; + window.location.hash = basePath; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Spaces' }]); + expect(container).toMatchInlineSnapshot(` +
+ Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `create space` page', async () => { + const basePath = '/some-base-path/spaces'; + window.location.hash = `${basePath}/create`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Spaces' }, + { text: 'Create' }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); + + it('mount() works for the `edit space` page', async () => { + const basePath = '/some-base-path/spaces'; + const spaceId = 'some-space'; + window.location.hash = `${basePath}/edit/${spaceId}`; + + const { setBreadcrumbs, container, unmount } = await mountApp(basePath, spaceId); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: `#${basePath}`, text: 'Spaces' }, + { href: `#/some-base-path/spaces/edit/${spaceId}`, text: `space with id some-space` }, + ]); + expect(container).toMatchInlineSnapshot(` +
+ Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"http":{"basePath":{"basePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","securityEnabled":true} +
+ `); + + unmount(); + + expect(container).toMatchInlineSnapshot(`
`); + }); +}); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx new file mode 100644 index 0000000000000..663237cfc2e8a --- /dev/null +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup } from 'src/core/public'; +import { SecurityLicense } from '../../../security/public'; +import { RegisterManagementAppArgs } from '../../../../../src/plugins/management/public'; +import { PluginsStart } from '../plugin'; +import { SpacesManager } from '../spaces_manager'; +import { SpacesGridPage } from './spaces_grid'; +import { ManageSpacePage } from './edit_space'; +import { Space } from '..'; + +interface CreateParams { + getStartServices: CoreSetup['getStartServices']; + spacesManager: SpacesManager; + securityLicense?: SecurityLicense; +} + +export const spacesManagementApp = Object.freeze({ + id: 'spaces', + create({ getStartServices, spacesManager, securityLicense }: CreateParams) { + return { + id: this.id, + order: 10, + title: i18n.translate('xpack.spaces.displayName', { + defaultMessage: 'Spaces', + }), + async mount({ basePath, element, setBreadcrumbs }) { + const [{ http, notifications, i18n: i18nStart, application }] = await getStartServices(); + const spacesBreadcrumbs = [ + { + text: i18n.translate('xpack.spaces.management.breadcrumb', { + defaultMessage: 'Spaces', + }), + href: `#${basePath}`, + }, + ]; + + const SpacesGridPageWithBreadcrumbs = () => { + setBreadcrumbs(spacesBreadcrumbs); + return ( + + ); + }; + + const CreateSpacePageWithBreadcrumbs = () => { + setBreadcrumbs([ + ...spacesBreadcrumbs, + { + text: i18n.translate('xpack.spaces.management.createSpaceBreadcrumb', { + defaultMessage: 'Create', + }), + }, + ]); + + return ( + + ); + }; + + const EditSpacePageWithBreadcrumbs = () => { + const { spaceId } = useParams<{ spaceId: string }>(); + + const onLoadSpace = (space: Space) => { + setBreadcrumbs([ + ...spacesBreadcrumbs, + { + text: space.name, + href: `#${basePath}/edit/${encodeURIComponent(space.id)}`, + }, + ]); + }; + + return ( + + ); + }; + + render( + + + + + + + + + + + + + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; + }, + } as RegisterManagementAppArgs; + }, +}); diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap similarity index 96% rename from x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap rename to x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 45daa03e94c2e..22d65f4600e05 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -40,6 +40,7 @@ exports[`NavControlPopover renders without crashing 1`] = ` } } id="headerSpacesMenuContent" + navigateToApp={[MockFunction]} onManageSpacesClick={[Function]} /> diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss b/x-pack/plugins/spaces/public/nav_control/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/_index.scss rename to x-pack/plugins/spaces/public/nav_control/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss b/x-pack/plugins/spaces/public/nav_control/_nav_control.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss rename to x-pack/plugins/spaces/public/nav_control/_nav_control.scss diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap rename to x-pack/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss b/x-pack/plugins/spaces/public/nav_control/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss rename to x-pack/plugins/spaces/public/nav_control/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss b/x-pack/plugins/spaces/public/nav_control/components/_spaces_description.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss rename to x-pack/plugins/spaces/public/nav_control/components/_spaces_description.scss diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss b/x-pack/plugins/spaces/public/nav_control/components/_spaces_menu.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss rename to x-pack/plugins/spaces/public/nav_control/components/_spaces_menu.scss diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx rename to x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx index 2dc6ae919c018..009b6aa89d089 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx @@ -12,6 +12,7 @@ describe('ManageSpacesButton', () => { it('renders as expected', () => { const component = ( { it(`doesn't render if user profile forbids managing spaces`, () => { const component = ( void; capabilities: Capabilities; + navigateToApp: ApplicationStart['navigateToApp']; } export class ManageSpacesButton extends Component { @@ -45,6 +45,7 @@ export class ManageSpacesButton extends Component { if (this.props.onClick) { this.props.onClick(); } - window.location.replace(getManageSpacesUrl()); + + this.props.navigateToApp('kibana', { path: '#/management/kibana/spaces' }); }; } diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx similarity index 88% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx rename to x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx index b6982a3d687a6..3a431ae0929b8 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_description.tsx @@ -6,7 +6,7 @@ import { EuiContextMenuPanel, EuiText } from '@elastic/eui'; import React, { FC } from 'react'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ApplicationStart } from 'src/core/public'; import { ManageSpacesButton } from './manage_spaces_button'; import { getSpacesFeatureDescription } from '../../constants'; @@ -14,6 +14,7 @@ interface Props { id: string; onManageSpacesClick: () => void; capabilities: Capabilities; + navigateToApp: ApplicationStart['navigateToApp']; } export const SpacesDescription: FC = (props: Props) => { @@ -34,6 +35,7 @@ export const SpacesDescription: FC = (props: Props) => { style={{ width: `100%` }} onClick={props.onManageSpacesClick} capabilities={props.capabilities} + navigateToApp={props.navigateToApp} />
diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx rename to x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx index 4d89f57d4ccf1..59656333f865e 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -13,7 +13,7 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, ReactElement } from 'react'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ApplicationStart } from 'src/core/public'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; import { Space } from '../../../common/model/space'; import { ManageSpacesButton } from './manage_spaces_button'; @@ -27,6 +27,7 @@ interface Props { onManageSpacesClick: () => void; intl: InjectedIntl; capabilities: Capabilities; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -166,6 +167,7 @@ class SpacesMenuUI extends Component { size="s" onClick={this.props.onManageSpacesClick} capabilities={this.props.capabilities} + navigateToApp={this.props.navigateToApp} /> ); }; diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/index.ts b/x-pack/plugins/spaces/public/nav_control/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/index.ts rename to x-pack/plugins/spaces/public/nav_control/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx rename to x-pack/plugins/spaces/public/nav_control/nav_control.tsx index 9ec070eff3fed..53d7038cec4d5 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx @@ -25,6 +25,7 @@ export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreSta spacesManager={spacesManager} anchorPosition="downLeft" capabilities={core.application.capabilities} + navigateToApp={core.application.navigateToApp} /> , targetDomElement diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx rename to x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index 5ce141abb713e..0e0a3473be7ff 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -23,6 +23,7 @@ describe('NavControlPopover', () => { spacesManager={(spacesManager as unknown) as SpacesManager} anchorPosition={'downRight'} capabilities={{ navLinks: {}, management: {}, catalogue: {}, spaces: { manage: true } }} + navigateToApp={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); @@ -54,6 +55,7 @@ describe('NavControlPopover', () => { spacesManager={(spacesManager as unknown) as SpacesManager} anchorPosition={'rightCenter'} capabilities={{ navLinks: {}, management: {}, catalogue: {}, spaces: { manage: true } }} + navigateToApp={jest.fn()} /> ); diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx rename to x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index 59c8052a644da..ef7eff437c86a 100644 --- a/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -11,7 +11,7 @@ import { EuiHeaderSectionItemButton, } from '@elastic/eui'; import React, { Component } from 'react'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ApplicationStart } from 'src/core/public'; import { Subscription } from 'rxjs'; import { Space } from '../../common/model/space'; import { SpaceAvatar } from '../space_avatar'; @@ -23,6 +23,7 @@ interface Props { spacesManager: SpacesManager; anchorPosition: PopoverAnchorPosition; capabilities: Capabilities; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -76,6 +77,7 @@ export class NavControlPopover extends Component { id={popoutContentId} onManageSpacesClick={this.toggleSpaceSelector} capabilities={this.props.capabilities} + navigateToApp={this.props.navigateToApp} /> ); } else { @@ -87,6 +89,7 @@ export class NavControlPopover extends Component { onSelectSpace={this.onSelectSpace} onManageSpacesClick={this.toggleSpaceSelector} capabilities={this.props.capabilities} + navigateToApp={this.props.navigateToApp} /> ); } diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/types.tsx b/x-pack/plugins/spaces/public/nav_control/types.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/nav_control/types.tsx rename to x-pack/plugins/spaces/public/nav_control/types.tsx diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts new file mode 100644 index 0000000000000..28f8433be6fd9 --- /dev/null +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { coreMock } from 'src/core/public/mocks'; +import { SpacesPlugin } from './plugin'; +import { homePluginMock } from '../../../../src/plugins/home/public/mocks'; +import { ManagementSection } from '../../../../src/plugins/management/public'; +import { managementPluginMock } from '../../../../src/plugins/management/public/mocks'; +import { advancedSettingsMock } from '../../../../src/plugins/advanced_settings/public/mocks'; + +describe('Spaces plugin', () => { + describe('#setup', () => { + it('should register the space selector app', () => { + const coreSetup = coreMock.createSetup(); + + const plugin = new SpacesPlugin(); + plugin.setup(coreSetup, {}); + + expect(coreSetup.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'space_selector', + chromeless: true, + appRoute: '/spaces/space_selector', + mount: expect.any(Function), + }) + ); + }); + + it('should register the management and feature catalogue sections when the management and home plugins are both available', () => { + const coreSetup = coreMock.createSetup(); + + const kibanaSection = new ManagementSection( + { + id: 'kibana', + title: 'Mock Kibana Section', + order: 1, + }, + jest.fn(), + jest.fn(), + jest.fn(), + coreSetup.getStartServices + ); + + const registerAppSpy = jest.spyOn(kibanaSection, 'registerApp'); + + const home = homePluginMock.createSetupContract(); + + const management = managementPluginMock.createSetupContract(); + management.sections.getSection.mockReturnValue(kibanaSection); + + const plugin = new SpacesPlugin(); + plugin.setup(coreSetup, { + management, + home, + }); + + expect(registerAppSpy).toHaveBeenCalledWith(expect.objectContaining({ id: 'spaces' })); + + expect(home.featureCatalogue.register).toHaveBeenCalledWith( + expect.objectContaining({ + category: 'admin', + icon: 'spacesApp', + id: 'spaces', + showOnHomePage: true, + }) + ); + }); + + it('should register the advanced settings components if the advanced_settings plugin is available', () => { + const coreSetup = coreMock.createSetup(); + const advancedSettings = advancedSettingsMock.createSetupContract(); + + const plugin = new SpacesPlugin(); + plugin.setup(coreSetup, { advancedSettings }); + + expect(advancedSettings.component.register.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "advanced_settings_page_title", + [Function], + true, + ], + Array [ + "advanced_settings_page_subtitle", + [Function], + true, + ], + ] + `); + }); + }); + + describe('#start', () => { + it('should register the spaces nav control', () => { + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + + const plugin = new SpacesPlugin(); + plugin.setup(coreSetup, {}); + + plugin.start(coreStart, {}); + + expect(coreStart.chrome.navControls.registerLeft).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx new file mode 100644 index 0000000000000..73dae84c1873b --- /dev/null +++ b/x-pack/plugins/spaces/public/plugin.tsx @@ -0,0 +1,120 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { HomePublicPluginSetup } from 'src/plugins/home/public'; +import { SavedObjectsManagementAction } from 'src/legacy/core_plugins/management/public'; +import { ManagementStart, ManagementSetup } from 'src/plugins/management/public'; +import { AdvancedSettingsSetup } from 'src/plugins/advanced_settings/public'; +import { SecurityPluginStart, SecurityPluginSetup } from '../../security/public'; +import { SpacesManager } from './spaces_manager'; +import { initSpacesNavControl } from './nav_control'; +import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; +import { CopySavedObjectsToSpaceService } from './copy_saved_objects_to_space'; +import { AdvancedSettingsService } from './advanced_settings'; +import { ManagementService } from './management'; +import { spaceSelectorApp } from './space_selector'; + +export interface PluginsSetup { + advancedSettings?: AdvancedSettingsSetup; + home?: HomePublicPluginSetup; + management?: ManagementSetup; + security?: SecurityPluginSetup; +} + +export interface PluginsStart { + management?: ManagementStart; + security?: SecurityPluginStart; +} + +interface LegacyAPI { + registerSavedObjectsManagementAction: (action: SavedObjectsManagementAction) => void; +} + +export type SpacesPluginSetup = ReturnType; +export type SpacesPluginStart = ReturnType; + +export class SpacesPlugin implements Plugin { + private spacesManager!: SpacesManager; + + private managementService?: ManagementService; + + public setup(core: CoreSetup, plugins: PluginsSetup) { + const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; + this.spacesManager = new SpacesManager(serverBasePath, core.http); + + if (plugins.home) { + plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + } + + if (plugins.management) { + this.managementService = new ManagementService(); + this.managementService.setup({ + management: plugins.management, + getStartServices: core.getStartServices, + spacesManager: this.spacesManager, + securityLicense: plugins.security?.license, + }); + } + + if (plugins.advancedSettings) { + const advancedSettingsService = new AdvancedSettingsService(); + advancedSettingsService.setup({ + getActiveSpace: () => this.spacesManager.getActiveSpace(), + componentRegistry: plugins.advancedSettings.component, + }); + } + + spaceSelectorApp.create({ + getStartServices: core.getStartServices, + application: core.application, + spacesManager: this.spacesManager, + }); + + return { + registerLegacyAPI: (legacyAPI: LegacyAPI) => { + const copySavedObjectsToSpaceService = new CopySavedObjectsToSpaceService(); + copySavedObjectsToSpaceService.setup({ + spacesManager: this.spacesManager, + managementSetup: { + savedObjects: { + registry: { + register: action => legacyAPI.registerSavedObjectsManagementAction(action), + has: () => { + throw new Error('not available in legacy shim'); + }, + get: () => { + throw new Error('not available in legacy shim'); + }, + }, + }, + }, + notificationsSetup: core.notifications, + }); + }, + }; + } + + public start(core: CoreStart, plugins: PluginsStart) { + initSpacesNavControl(this.spacesManager, core); + + if (this.managementService) { + this.managementService.start({ capabilities: core.application.capabilities }); + } + + return { + activeSpace$: this.spacesManager.onActiveSpaceChange$, + getActiveSpace: () => this.spacesManager.getActiveSpace(), + }; + } + + public stop() { + if (this.managementService) { + this.managementService.stop(); + this.managementService = undefined; + } + } +} diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap b/x-pack/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap rename to x-pack/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts b/x-pack/plugins/spaces/public/space_avatar/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/index.ts rename to x-pack/plugins/spaces/public/space_avatar/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts b/x-pack/plugins/spaces/public/space_avatar/space_attributes.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts rename to x-pack/plugins/spaces/public/space_avatar/space_attributes.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts b/x-pack/plugins/spaces/public/space_avatar/space_attributes.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts rename to x-pack/plugins/spaces/public/space_avatar/space_attributes.ts diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx b/x-pack/plugins/spaces/public/space_avatar/space_avatar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx rename to x-pack/plugins/spaces/public/space_avatar/space_avatar.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx b/x-pack/plugins/spaces/public/space_avatar/space_avatar.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx rename to x-pack/plugins/spaces/public/space_avatar/space_avatar.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap b/x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap rename to x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss b/x-pack/plugins/spaces/public/space_selector/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/_index.scss rename to x-pack/plugins/spaces/public/space_selector/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss b/x-pack/plugins/spaces/public/space_selector/_space_selector.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss rename to x-pack/plugins/spaces/public/space_selector/_space_selector.scss diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss b/x-pack/plugins/spaces/public/space_selector/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss rename to x-pack/plugins/spaces/public/space_selector/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss b/x-pack/plugins/spaces/public/space_selector/components/_space_card.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss rename to x-pack/plugins/spaces/public/space_selector/components/_space_card.scss diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss b/x-pack/plugins/spaces/public/space_selector/components/_space_cards.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss rename to x-pack/plugins/spaces/public/space_selector/components/_space_cards.scss diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts b/x-pack/plugins/spaces/public/space_selector/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts rename to x-pack/plugins/spaces/public/space_selector/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_card.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx rename to x-pack/plugins/spaces/public/space_selector/components/space_card.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_card.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx rename to x-pack/plugins/spaces/public/space_selector/components/space_card.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx rename to x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx rename to x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx diff --git a/x-pack/plugins/spaces/public/space_selector/index.tsx b/x-pack/plugins/spaces/public/space_selector/index.tsx new file mode 100644 index 0000000000000..b99689cbabab1 --- /dev/null +++ b/x-pack/plugins/spaces/public/space_selector/index.tsx @@ -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 { spaceSelectorApp } from './space_selector_app'; diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx similarity index 82% rename from x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx rename to x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx index b4d0f96307500..c8173de1661be 100644 --- a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Space } from '../../common/model/space'; -import { spacesManagerMock } from '../spaces_manager/mocks'; import { SpaceSelector } from './space_selector'; +import { spacesManagerMock } from '../spaces_manager/mocks'; function getSpacesManager(spaces: Space[] = []) { const manager = spacesManagerMock.create(); @@ -18,9 +18,7 @@ function getSpacesManager(spaces: Space[] = []) { test('it renders without crashing', () => { const spacesManager = getSpacesManager(); - const component = shallowWithIntl( - - ); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); @@ -36,9 +34,7 @@ test('it queries for spaces when loaded', () => { const spacesManager = getSpacesManager(spaces); - shallowWithIntl( - - ); + shallowWithIntl(); return Promise.resolve().then(() => { expect(spacesManager.getSpaces).toHaveBeenCalledTimes(1); diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx rename to x-pack/plugins/spaces/public/space_selector/space_selector.tsx index 206d38454fa8c..b63de399b95c9 100644 --- a/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx @@ -18,16 +18,18 @@ import { EuiTitle, EuiLoadingSpinner, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { SpacesManager } from '../spaces_manager'; +import ReactDOM from 'react-dom'; +import { CoreStart } from 'src/core/public'; import { Space } from '../../common/model/space'; import { SpaceCards } from './components'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../common/constants'; +import { SpacesManager } from '../spaces_manager'; interface Props { spacesManager: SpacesManager; - intl: InjectedIntl; } interface State { @@ -36,7 +38,7 @@ interface State { spaces: Space[]; } -class SpaceSelectorUI extends Component { +export class SpaceSelector extends Component { private headerRef?: HTMLElement | null; constructor(props: Props) { super(props); @@ -152,7 +154,6 @@ class SpaceSelectorUI extends Component { } public getSearchField = () => { - const { intl } = this.props; if (!this.state.spaces || this.state.spaces.length < SPACE_SEARCH_COUNT_THRESHOLD) { return null; } @@ -162,8 +163,7 @@ class SpaceSelectorUI extends Component { // @ts-ignore onSearch doesn't exist on EuiFieldSearch { }; } -export const SpaceSelector = injectI18n(SpaceSelectorUI); +export const renderSpaceSelectorApp = (i18nStart: CoreStart['i18n'], el: Element, props: Props) => { + ReactDOM.render( + + + , + el + ); + return () => ReactDOM.unmountComponentAtNode(el); +}; diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx new file mode 100644 index 0000000000000..29ead8d34994d --- /dev/null +++ b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, AppMountParameters } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; +import { SpacesManager } from '../spaces_manager'; + +interface CreateDeps { + application: CoreSetup['application']; + spacesManager: SpacesManager; + getStartServices: CoreSetup['getStartServices']; +} + +export const spaceSelectorApp = Object.freeze({ + id: 'space_selector', + create({ application, getStartServices, spacesManager }: CreateDeps) { + application.register({ + id: this.id, + title: i18n.translate('xpack.spaces.spaceSelector.appTitle', { + defaultMessage: 'Select a space', + }), + chromeless: true, + appRoute: '/spaces/space_selector', + mount: async (params: AppMountParameters) => { + const [[coreStart], { renderSpaceSelectorApp }] = await Promise.all([ + getStartServices(), + import('./space_selector'), + ]); + return renderSpaceSelectorApp(coreStart.i18n, params.element, { spacesManager }); + }, + }); + }, +}); diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts b/x-pack/plugins/spaces/public/spaces_manager/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts rename to x-pack/plugins/spaces/public/spaces_manager/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts b/x-pack/plugins/spaces/public/spaces_manager/mocks.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts rename to x-pack/plugins/spaces/public/spaces_manager/mocks.ts diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts rename to x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts rename to x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts similarity index 95% rename from x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts rename to x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts index e9c738cf40c69..e151dcd4f9368 100644 --- a/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -6,12 +6,12 @@ import { Observable, BehaviorSubject } from 'rxjs'; import { skipWhile } from 'rxjs/operators'; import { HttpSetup } from 'src/core/public'; -import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from 'src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; import { ENTER_SPACE_PATH } from '../../common/constants'; -import { addSpaceIdToPath } from '../../../../../plugins/spaces/common'; import { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; +import { addSpaceIdToPath } from '../../common'; export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts index 18f7575ff75d6..77eb3e9c73980 100644 --- a/x-pack/plugins/spaces/server/index.ts +++ b/x-pack/plugins/spaces/server/index.ts @@ -17,6 +17,7 @@ import { Plugin } from './plugin'; export { SpacesPluginSetup } from './plugin'; export { SpacesServiceSetup } from './spaces_service'; +export { Space } from '../common/model/space'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 52ff7eaee3d68..90c2da6e69df8 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -16,7 +16,6 @@ import { import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { SecurityPluginSetup } from '../../security/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { XPackMainPlugin } from '../../../legacy/plugins/xpack_main/server/xpack_main'; import { createDefaultSpace } from './lib/create_default_space'; // @ts-ignore import { AuditLogger } from '../../../../server/lib/audit_logger'; @@ -31,6 +30,7 @@ import { toggleUICapabilities } from './lib/toggle_ui_capabilities'; import { initSpacesRequestInterceptors } from './lib/request_interceptors'; import { initExternalSpacesApi } from './routes/api/external'; import { initInternalSpacesApi } from './routes/api/internal'; +import { initSpacesViewsRoutes } from './routes/views'; /** * Describes a set of APIs that is available in the legacy platform only and required by this plugin @@ -44,7 +44,6 @@ export interface LegacyAPI { legacyConfig: { kibanaIndex: string; }; - xpackMain: XPackMainPlugin; } export interface PluginsSetup { @@ -109,6 +108,12 @@ export class Plugin { config$: this.config$, }); + const viewRouter = core.http.createRouter(); + initSpacesViewsRoutes({ + viewRouter, + cspHeader: core.http.csp.header, + }); + const externalRouter = core.http.createRouter(); initExternalSpacesApi({ externalRouter, diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts index dfeb094e34e25..812b02e94f591 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts @@ -104,7 +104,6 @@ export const createLegacyAPI = ({ kibanaIndex: '', }, auditLogger: {} as any, - xpackMain: {} as any, savedObjects: savedObjectsService, }; diff --git a/x-pack/plugins/spaces/server/routes/views/index.ts b/x-pack/plugins/spaces/server/routes/views/index.ts new file mode 100644 index 0000000000000..2a346c7e5241a --- /dev/null +++ b/x-pack/plugins/spaces/server/routes/views/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { IRouter } from 'src/core/server'; + +export interface ViewRouteDeps { + viewRouter: IRouter; + cspHeader: string; +} + +export function initSpacesViewsRoutes(deps: ViewRouteDeps) { + deps.viewRouter.get( + { + path: '/spaces/space_selector', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + headers: { + 'Content-Security-Policy': deps.cspHeader, + }, + body: await context.core.rendering.render({ includeUserSettings: true }), + }); + } + ); +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1c2a0fc3d5ac8..e9a5d9611c806 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -352,7 +352,6 @@ "common.ui.flotCharts.thuLabel": "木", "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", - "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.paginateControls.pageSizeLabel": "ページサイズ", "common.ui.paginateControls.scrollTopButtonLabel": "最上部に移動", "common.ui.savedObjects.confirmModal.overwriteButtonLabel": "上書き", @@ -1436,8 +1435,8 @@ "kbn.management.indexPattern.confirmOverwriteTitle": "{type} を上書きしますか?", "kbn.management.indexPattern.sectionsHeader": "インデックスパターン", "kbn.management.indexPattern.titleExistsLabel": "「{title}」というタイトルのインデックスパターンが既に存在します。", - "kbn.management.indexPatternHeader": "インデックスパターン", - "kbn.management.indexPatternLabel": "Elasticsearch からのデータの取得に役立つインデックスパターンを管理します。", + "management.indexPatternHeader": "インデックスパターン", + "management.indexPatternLabel": "Elasticsearch からのデータの取得に役立つインデックスパターンを管理します。", "kbn.management.indexPatternList.createButton.betaLabel": "ベータ", "kbn.management.indexPatternPrompt.exampleOne": "チャートを作成したりコンテンツを素早くクエリできるように log-west-001 という名前の単一のデータソースをインデックスします。", "kbn.management.indexPatternPrompt.exampleOneTitle": "単一のデータソース", @@ -1556,9 +1555,9 @@ "kbn.management.objects.objectsTable.table.typeFilterName": "タイプ", "kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません", "kbn.management.objects.parsingFieldErrorMessage": "{fieldName} をインデックスパターン {indexName} 用にパース中にエラーが発生しました: {errorMessage}", - "kbn.management.objects.savedObjectsDescription": "保存された検索、ビジュアライゼーション、ダッシュボードのインポート、エクスポート、管理を行います", + "management.objects.savedObjectsDescription": "保存された検索、ビジュアライゼーション、ダッシュボードのインポート、エクスポート、管理を行います", "kbn.management.objects.savedObjectsSectionLabel": "保存されたオブジェクト", - "kbn.management.objects.savedObjectsTitle": "保存されたオブジェクト", + "management.objects.savedObjectsTitle": "保存されたオブジェクト", "kbn.management.objects.view.cancelButtonAriaLabel": "キャンセル", "kbn.management.objects.view.cancelButtonLabel": "キャンセル", "kbn.management.objects.view.deleteItemButtonLabel": "{title} を削除", @@ -1576,8 +1575,8 @@ "kbn.management.objects.view.viewItemTitle": "{title} を表示", "kbn.management.savedObjects.editBreadcrumb": "{savedObjectType} を編集", "kbn.management.savedObjects.indexBreadcrumb": "保存されたオブジェクト", - "kbn.management.settings.advancedSettingsDescription": "Kibana の動作を管理する設定を直接変更します。", - "kbn.management.settings.advancedSettingsLabel": "高度な設定", + "advancedSettings.advancedSettingsDescription": "Kibana の動作を管理する設定を直接変更します。", + "advancedSettings.advancedSettingsLabel": "高度な設定", "kbn.management.settings.breadcrumb": "高度な設定", "kbn.management.settings.callOutCautionDescription": "これらの設定は非常に上級ユーザー向けなのでご注意ください。ここでの変更は Kibana の重要な部分に不具合を生じさせる可能性があります。これらの設定はドキュメントに記載されていなかったり、サポートされていなかったり、実験的であったりします。フィールドにデフォルトの値が設定されている場合、そのフィールドを未入力のままにするとデフォルトに戻り、他の構成により利用できない可能性があります。カスタム設定を削除すると、Kibana の構成から永久に削除されます。", "kbn.management.settings.callOutCautionTitle": "注意:不具合が起こる可能性があります", @@ -2792,7 +2791,6 @@ "timelion.noFunctionErrorMessage": "そのような関数はありません: {name}", "timelion.panels.noRenderFunctionErrorMessage": "パネルにはレンダリング関数が必要です", "timelion.panels.timechart.unknownIntervalErrorMessage": "不明な間隔", - "timelion.registerFeatureDescription": "時系列データを分析して結果を可視化するには、式言語を使用してください。", "timelion.requestHandlerErrorTitle": "Timelion リクエストエラー", "timelion.savedObjects.howToSaveAsNewDescription": "Kibana の以前のバージョンでは、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。今後この操作を行うには、「新規 {savedObjectName} として保存」を使用します。", "timelion.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c24f2952ef2ac..201e3c35ee282 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -352,7 +352,6 @@ "common.ui.flotCharts.thuLabel": "周四", "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", - "common.ui.modals.cancelButtonLabel": "取消", "common.ui.paginateControls.pageSizeLabel": "页面大小", "common.ui.paginateControls.scrollTopButtonLabel": "滚动至顶部", "common.ui.savedObjects.confirmModal.overwriteButtonLabel": "覆盖", @@ -1436,8 +1435,8 @@ "kbn.management.indexPattern.confirmOverwriteTitle": "覆盖“{type}”?", "kbn.management.indexPattern.sectionsHeader": "索引模式", "kbn.management.indexPattern.titleExistsLabel": "具有标题 “{title}” 的索引模式已存在。", - "kbn.management.indexPatternHeader": "索引模式", - "kbn.management.indexPatternLabel": "管理帮助从 Elasticsearch 检索数据的索引模式。", + "management.indexPatternHeader": "索引模式", + "management.indexPatternLabel": "管理帮助从 Elasticsearch 检索数据的索引模式。", "kbn.management.indexPatternList.createButton.betaLabel": "公测版", "kbn.management.indexPatternPrompt.exampleOne": "索引单个称作 log-west-001 的数据源,以便可以快速地构建图表或查询其内容。", "kbn.management.indexPatternPrompt.exampleOneTitle": "单数据源", @@ -1556,9 +1555,9 @@ "kbn.management.objects.objectsTable.table.typeFilterName": "类型", "kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage": "找不到已保存对象", "kbn.management.objects.parsingFieldErrorMessage": "为索引模式 “{indexName}” 解析 “{fieldName}” 时发生错误:{errorMessage}", - "kbn.management.objects.savedObjectsDescription": "导入、导出和管理您的已保存搜索、可视化和仪表板。", + "management.objects.savedObjectsDescription": "导入、导出和管理您的已保存搜索、可视化和仪表板。", "kbn.management.objects.savedObjectsSectionLabel": "已保存对象", - "kbn.management.objects.savedObjectsTitle": "已保存对象", + "management.objects.savedObjectsTitle": "已保存对象", "kbn.management.objects.view.cancelButtonAriaLabel": "取消", "kbn.management.objects.view.cancelButtonLabel": "取消", "kbn.management.objects.view.deleteItemButtonLabel": "删除“{title}”", @@ -1576,8 +1575,8 @@ "kbn.management.objects.view.viewItemTitle": "查看“{title}”", "kbn.management.savedObjects.editBreadcrumb": "编辑 {savedObjectType}", "kbn.management.savedObjects.indexBreadcrumb": "已保存对象", - "kbn.management.settings.advancedSettingsDescription": "直接编辑在 Kibana 中控制行为的设置。", - "kbn.management.settings.advancedSettingsLabel": "高级设置", + "advancedSettings.advancedSettingsDescription": "直接编辑在 Kibana 中控制行为的设置。", + "advancedSettings.advancedSettingsLabel": "高级设置", "kbn.management.settings.breadcrumb": "高级设置", "kbn.management.settings.callOutCautionDescription": "此处请谨慎操作,这些设置仅供高级用户使用。您在这里所做的更改可能使 Kibana 的大部分功能出现问题。这些设置有一部分可能未在文档中说明、不受支持或是实验性设置。如果字段有默认值,将字段留空会将其设置为默认值,其他配置指令可能不接受其默认值。删除定制设置会将其从 Kibana 的配置中永久删除。", "kbn.management.settings.callOutCautionTitle": "注意:在这里您可能会使问题出现", @@ -2792,7 +2791,6 @@ "timelion.noFunctionErrorMessage": "没有此类函数:{name}", "timelion.panels.noRenderFunctionErrorMessage": "面板必须具有渲染函数", "timelion.panels.timechart.unknownIntervalErrorMessage": "时间间隔未知", - "timelion.registerFeatureDescription": "使用表达式语言分析时间序列数据,并将结果可视化。", "timelion.requestHandlerErrorTitle": "Timelion 请求错误", "timelion.savedObjects.howToSaveAsNewDescription": "在 Kibana 的以前版本中,更改 {savedObjectName} 的名称将创建具有新名称的副本。使用“另存为新的 {savedObjectName}” 复选框可立即达到此目的。", "timelion.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx index 3000191218932..fecf846ed6c9a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx @@ -73,7 +73,7 @@ export function getActionType(): ActionTypeModel { ) ); } - if (!action.secrets.user) { + if (!action.secrets.user && action.secrets.password) { errors.user.push( i18n.translate( 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHostText', @@ -83,7 +83,7 @@ export function getActionType(): ActionTypeModel { ) ); } - if (!action.secrets.password) { + if (!action.secrets.password && action.secrets.user) { errors.password.push( i18n.translate( 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredPasswordText', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index 9b6b4a2cf1f22..04090d2c6428d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -7,7 +7,7 @@ import React, { useContext, createContext } from 'react'; import { HttpSetup, IUiSettingsClient, ToastsApi } from 'kibana/public'; import { ChartsPluginSetup } from 'src/plugins/charts/public'; -import { FieldFormatsRegistry } from 'src/plugins/data/common/field_formats/static'; +import { DataPublicPluginSetup } from 'src/plugins/data/public'; import { TypeRegistry } from '../type_registry'; import { AlertTypeModel, ActionTypeModel } from '../../types'; @@ -24,7 +24,7 @@ export interface AlertsContextValue { 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' >; charts?: ChartsPluginSetup; - dataFieldsFormats?: Pick; + dataFieldsFormats?: DataPublicPluginSetup['fieldFormats']; } const AlertsContext = createContext(null as any); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts index 9a878ff0bf798..1b267f6c4976f 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts @@ -68,7 +68,7 @@ function webhookHandler(request: WebhookRequest, h: any) { return validateRequestUsesMethod(request, h, 'post'); case 'success_put_method': return validateRequestUsesMethod(request, h, 'put'); - case 'faliure': + case 'failure': return htmlResponse(h, 500, `Error`); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 841c96acdc3b1..da83dbf8c47e2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -212,8 +212,8 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/error calling webhook, invalid response/); - expect(result.serviceMessage).to.eql('[400] Bad Request'); + expect(result.message).to.match(/error calling webhook, retry later/); + expect(result.serviceMessage).to.eql('[500] Internal Server Error'); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts index bafca30abf28a..5007cfa6cf044 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts @@ -84,6 +84,62 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte } }); + it('should still be able to disable alert when AAD is broken', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: true })) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert'); + + await supertest + .put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + + const response = await alertUtils.getDisableRequest(createdAlert.id); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + // Ensure task still exists + await getScheduledTask(createdAlert.scheduledTaskId); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(204); + expect(response.body).to.eql(''); + try { + await getScheduledTask(createdAlert.scheduledTaskId); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.status).to.eql(404); + } + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'alert', + id: createdAlert.id, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix('other')}/api/alert`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts index 9df1f955232b1..d89172515757b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts @@ -89,6 +89,67 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex } }); + it('should still be able to enable alert when AAD is broken', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert'); + + await supertest + .put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + + const response = await alertUtils.getEnableRequest(createdAlert.id); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(204); + expect(response.body).to.eql(''); + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .expect(200); + expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); + const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduledTaskId); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: createdAlert.id, + spaceId: space.id, + }); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'alert', + id: createdAlert.id, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix('other')}/api/alert`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index d99ab794cd28f..65ffa9ebe9dfa 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -158,7 +158,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdBy: 'elastic', throttle: '1m', updatedBy: 'elastic', - apiKeyOwner: 'elastic', + apiKeyOwner: null, muteAll: false, mutedInstanceIds: [], createdAt: match.createdAt, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts index b54147348d9a3..cd821a739a9eb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts @@ -74,6 +74,60 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte } }); + it('should still be able to update API key when AAD is broken', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert'); + + await supertest + .put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .send({ + attributes: { + name: 'bar', + }, + }) + .expect(200); + + const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(204); + expect(response.body).to.eql(''); + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .expect(200); + expect(updatedAlert.apiKeyOwner).to.eql(user.username); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'alert', + id: createdAlert.id, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix('other')}/api/alert`) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts new file mode 100644 index 0000000000000..5122a74d53b72 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { URL, format as formatUrl } from 'url'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { + getExternalServiceSimulatorPath, + ExternalServiceSimulator, +} from '../../../../common/fixtures/plugins/actions'; + +// eslint-disable-next-line import/no-default-export +export default function webhookTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + + async function createWebhookAction( + urlWithCreds: string, + config: Record> = {} + ): Promise { + const url = formatUrl(new URL(urlWithCreds), { auth: false }); + const composedConfig = { + headers: { + 'Content-Type': 'text/plain', + }, + ...config, + url, + }; + + const { body: createdAction } = await supertest + .post('/api/action') + .set('kbn-xsrf', 'test') + .send({ + name: 'A generic Webhook action', + actionTypeId: '.webhook', + secrets: {}, + config: composedConfig, + }) + .expect(200); + + return createdAction.id; + } + + describe('webhook action', () => { + let webhookSimulatorURL: string = ''; + + // need to wait for kibanaServer to settle ... + before(() => { + webhookSimulatorURL = kibanaServer.resolveUrl( + getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK) + ); + }); + + after(() => esArchiver.unload('empty_kibana')); + + it('webhook can be executed without username and password', async () => { + const webhookActionId = await createWebhookAction(webhookSimulatorURL); + const { body: result } = await supertest + .post(`/api/action/${webhookActionId}/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'success', + }, + }) + .expect(200); + + expect(result.status).to.eql('ok'); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index accee08a00c61..fb2be8c86f4e8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -17,6 +17,7 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./builtin_action_types/es_index')); + loadTestFile(require.resolve('./builtin_action_types/webhook')); loadTestFile(require.resolve('./type_not_enabled')); }); } diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index 5ffab7e057aac..7195b8680a286 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -104,7 +104,7 @@ export default function({ getService }) { it('should require index or indices to be provided', async () => { const { body } = await deleteIndex().expect(400); - expect(body.message).to.contain('index / indices is missing'); + expect(body.message).to.contain('expected value of type [string]'); }); }); @@ -144,7 +144,7 @@ export default function({ getService }) { it('should allow to define the number of segments', async () => { const index = await createIndex(); - await forceMerge(index, { max_num_segments: 1 }).expect(200); + await forceMerge(index, { maxNumSegments: 1 }).expect(200); }); }); diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.js b/x-pack/test/api_integration/apis/management/index_management/templates.js index b404f7336738a..d9344846ebb91 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_management/templates.js @@ -92,14 +92,16 @@ export default function({ getService }) { await createTemplate(payload).expect(409); }); - it('should handle ES errors', async () => { + it('should validate the request payload', async () => { const templateName = `template-${getRandomString()}`; const payload = getTemplatePayload(templateName); delete payload.indexPatterns; // index patterns are required const { body } = await createTemplate(payload); - expect(body.message).to.contain('index patterns are missing'); + expect(body.message).to.contain( + '[request body.indexPatterns]: expected value of type [array] ' + ); }); }); diff --git a/x-pack/test/api_integration/apis/management/remote_clusters/remote_clusters.js b/x-pack/test/api_integration/apis/management/remote_clusters/remote_clusters.js index 947e28cf11153..677d22ff74984 100644 --- a/x-pack/test/api_integration/apis/management/remote_clusters/remote_clusters.js +++ b/x-pack/test/api_integration/apis/management/remote_clusters/remote_clusters.js @@ -57,6 +57,7 @@ export default function({ getService }) { .send({ name: 'test_cluster', seeds: [NODE_SEED], + skipUnavailable: false, }) .expect(409); @@ -183,17 +184,11 @@ export default function({ getService }) { { name: 'test_cluster_doesnt_exist', error: { - isBoom: true, - isServer: false, - data: null, - output: { + status: 404, + payload: { message: 'There is no remote cluster with that name.' }, + options: { statusCode: 404, - payload: { - statusCode: 404, - error: 'Not Found', - message: 'There is no remote cluster with that name.', - }, - headers: {}, + body: { message: 'There is no remote cluster with that name.' }, }, }, }, diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts new file mode 100644 index 0000000000000..1b8c8299a8ac6 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts @@ -0,0 +1,174 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('classification creation', function() { + this.tags(['smoke']); + before(async () => { + await esArchiver.load('ml/bm_classification'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await esArchiver.unload('ml/bm_classification'); + }); + + const testDataList = [ + { + suiteTitle: 'bank marketing', + jobType: 'classification', + jobId: `bm_1_${Date.now()}`, + jobDescription: + "Classification job based on 'bank-marketing' dataset with dependentVariable 'y' and trainingPercent '20'", + source: 'bank-marketing*', + get destinationIndex(): string { + return `dest_${this.jobId}`; + }, + dependentVariable: 'y', + trainingPercent: '20', + modelMemory: '105mb', + createIndexPattern: true, + expected: { + row: { + type: 'classification', + status: 'stopped', + progress: '100', + }, + }, + }, + ]; + for (const testData of testDataList) { + describe(`${testData.suiteTitle}`, function() { + after(async () => { + await ml.api.deleteIndices(testData.destinationIndex); + }); + + it('loads the data frame analytics page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataFrameAnalytics(); + }); + + it('loads the job creation flyout', async () => { + await ml.dataFrameAnalytics.startAnalyticsCreation(); + }); + + it('selects the job type', async () => { + await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); + await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + }); + + it('inputs the job id', async () => { + await ml.dataFrameAnalyticsCreation.assertJobIdInputExists(); + await ml.dataFrameAnalyticsCreation.setJobId(testData.jobId); + }); + + it('inputs the job description', async () => { + await ml.dataFrameAnalyticsCreation.assertJobDescriptionInputExists(); + await ml.dataFrameAnalyticsCreation.setJobDescription(testData.jobDescription); + }); + + it('selects the source index', async () => { + await ml.dataFrameAnalyticsCreation.assertSourceIndexInputExists(); + await ml.dataFrameAnalyticsCreation.selectSourceIndex(testData.source); + }); + + it('inputs the destination index', async () => { + await ml.dataFrameAnalyticsCreation.assertDestIndexInputExists(); + await ml.dataFrameAnalyticsCreation.setDestIndex(testData.destinationIndex); + }); + + it('inputs the dependent variable', async () => { + await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); + await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); + }); + + it('inputs the training percent', async () => { + await ml.dataFrameAnalyticsCreation.assertTrainingPercentInputExists(); + await ml.dataFrameAnalyticsCreation.setTrainingPercent(testData.trainingPercent); + }); + + it('inputs the model memory limit', async () => { + await ml.dataFrameAnalyticsCreation.assertModelMemoryInputExists(); + await ml.dataFrameAnalyticsCreation.setModelMemory(testData.modelMemory); + }); + + it('sets the create index pattern switch', async () => { + await ml.dataFrameAnalyticsCreation.assertCreateIndexPatternSwitchExists(); + await ml.dataFrameAnalyticsCreation.setCreateIndexPatternSwitchState( + testData.createIndexPattern + ); + }); + + it('creates the analytics job', async () => { + await ml.dataFrameAnalyticsCreation.assertCreateButtonExists(); + await ml.dataFrameAnalyticsCreation.createAnalyticsJob(); + }); + + it('starts the analytics job', async () => { + await ml.dataFrameAnalyticsCreation.assertStartButtonExists(); + await ml.dataFrameAnalyticsCreation.startAnalyticsJob(); + }); + + it('closes the create job flyout', async () => { + await ml.dataFrameAnalyticsCreation.assertCloseButtonExists(); + await ml.dataFrameAnalyticsCreation.closeCreateAnalyticsJobFlyout(); + }); + + it('finishes analytics processing', async () => { + await ml.dataFrameAnalytics.waitForAnalyticsCompletion(testData.jobId); + }); + + it('displays the analytics table', async () => { + await ml.dataFrameAnalytics.assertAnalyticsTableExists(); + }); + + it('displays the stats bar', async () => { + await ml.dataFrameAnalytics.assertAnalyticsStatsBarExists(); + }); + + it('displays the created job in the analytics table', async () => { + await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); + await ml.dataFrameAnalyticsTable.filterWithSearchString(testData.jobId); + const rows = await ml.dataFrameAnalyticsTable.parseAnalyticsTable(); + const filteredRows = rows.filter(row => row.id === testData.jobId); + expect(filteredRows).to.have.length( + 1, + `Filtered analytics table should have 1 row for job id '${testData.jobId}' (got matching items '${filteredRows}')` + ); + }); + + it('displays details for the created job in the analytics table', async () => { + await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { + id: testData.jobId, + description: testData.jobDescription, + sourceIndex: testData.source, + destinationIndex: testData.destinationIndex, + type: testData.expected.row.type, + status: testData.expected.row.status, + progress: testData.expected.row.progress, + }); + }); + + it('creates the destination index and writes results to it', async () => { + await ml.api.assertIndicesExist(testData.destinationIndex); + await ml.api.assertIndicesNotEmpty(testData.destinationIndex); + }); + + it('displays the results view for created job', async () => { + await ml.dataFrameAnalyticsTable.openResultsView(); + await ml.dataFrameAnalytics.assertClassificationEvaluatePanelElementsExists(); + await ml.dataFrameAnalytics.assertClassificationTablePanelExists(); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts index dd8de77e6d5d0..fda0c5d203f2e 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts @@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./outlier_detection_creation')); loadTestFile(require.resolve('./regression_creation')); + loadTestFile(require.resolve('./classification_creation')); }); } diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts index 1a514f4ad44e5..6a01afe6183ed 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts @@ -11,7 +11,7 @@ export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('outlier detection creation', function() { + describe('regression creation', function() { this.tags(['smoke']); before(async () => { await esArchiver.load('ml/egs_regression'); diff --git a/x-pack/test/functional/apps/security/security.js b/x-pack/test/functional/apps/security/security.ts similarity index 54% rename from x-pack/test/functional/apps/security/security.js rename to x-pack/test/functional/apps/security/security.ts index 37b01ff61f5af..2096a7755e01d 100644 --- a/x-pack/test/functional/apps/security/security.js +++ b/x-pack/test/functional/apps/security/security.ts @@ -5,11 +5,15 @@ */ import expect from '@kbn/expect'; +import { parse } from 'url'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function({ getService, getPageObjects }) { +export default function({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['security']); + const PageObjects = getPageObjects(['security', 'spaceSelector']); const testSubjects = getService('testSubjects'); + const spaces = getService('spaces'); describe('Security', function() { this.tags('smoke'); @@ -46,6 +50,37 @@ export default function({ getService, getPageObjects }) { const logoutMessage = await testSubjects.getVisibleText('loginInfoMessage'); expect(logoutMessage).to.eql('You have logged out of Kibana.'); }); + + describe('within a non-default space', async () => { + before(async () => { + await PageObjects.security.forceLogout(); + + await spaces.create({ + id: 'some-space', + name: 'Some non-default space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spaces.delete('some-space'); + }); + + it('logging out of a non-default space redirects to the login page at the server root', async () => { + await PageObjects.security.login(null, null, { + expectSpaceSelector: true, + }); + + await PageObjects.spaceSelector.clickSpaceCard('some-space'); + await PageObjects.spaceSelector.expectHomePage('some-space'); + + await PageObjects.security.logout(); + + const currentUrl = await browser.getCurrentUrl(); + const url = parse(currentUrl); + expect(url.pathname).to.eql('/login'); + }); + }); }); }); } diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts index d71d197a6ea19..9ca314ba5ec18 100644 --- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts +++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts @@ -66,7 +66,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`can navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/list', { + await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); @@ -75,7 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`can navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/create', { + await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); @@ -84,10 +84,14 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`can navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/edit/default', { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); + await PageObjects.common.navigateToActualUrl( + 'kibana', + 'management/kibana/spaces/edit/default', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); await testSubjects.existOrFail('spaces-edit-page'); }); @@ -136,35 +140,39 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it(`doesn't display Spaces management section`, async () => { await PageObjects.settings.navigateTo(); - await testSubjects.existOrFail('objects'); // this ensures we've gotten to the management page + await testSubjects.existOrFail('management-landing'); // this ensures we've gotten to the management page await testSubjects.missingOrFail('spaces'); }); it(`can't navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/list', { + await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('homeApp'); + await testSubjects.existOrFail('management-landing'); }); it(`can't navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/create', { + await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('homeApp'); + await testSubjects.existOrFail('management-landing'); }); it(`can't navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/spaces/edit/default', { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); + await PageObjects.common.navigateToActualUrl( + 'kibana', + 'management/kibana/spaces/edit/default', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); - await testSubjects.existOrFail('homeApp'); + await testSubjects.existOrFail('management-landing'); }); }); }); diff --git a/x-pack/test/functional/es_archives/ml/bm_classification/data.json.gz b/x-pack/test/functional/es_archives/ml/bm_classification/data.json.gz new file mode 100644 index 0000000000000..12ccf6ae60512 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/bm_classification/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/bm_classification/mappings.json b/x-pack/test/functional/es_archives/ml/bm_classification/mappings.json new file mode 100644 index 0000000000000..9d2cca22bf300 --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/bm_classification/mappings.json @@ -0,0 +1,1548 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "bank-marketing", + "mappings": { + "properties": { + "age": { + "type": "integer" + }, + "balance": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "campaign": { + "type": "integer" + }, + "cons_conf_idx": { + "type": "float" + }, + "contact": { + "type": "keyword" + }, + "day": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "day_of_week": { + "type": "keyword" + }, + "default": { + "type": "keyword" + }, + "duration": { + "type": "integer" + }, + "education": { + "type": "keyword" + }, + "emp_var_rate": { + "type": "float" + }, + "euribor3m": { + "type": "float" + }, + "housing": { + "type": "keyword" + }, + "job": { + "type": "keyword" + }, + "loan": { + "type": "keyword" + }, + "marital": { + "type": "keyword" + }, + "month": { + "type": "keyword" + }, + "nr_employed": { + "type": "integer" + }, + "pdays": { + "type": "integer" + }, + "poutcome": { + "type": "keyword" + }, + "previous": { + "type": "integer" + }, + "y": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "c0c235fba02ebd2a2412bcda79009b58", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "e588043a01d3d43477e7cad7efa0f5d8", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "84b320fd67209906333ffce261128462", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-status": "0367e4d775814b56a4bee29384f9aafe", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-services-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "metric": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "ignore_above": 256, + "type": "keyword" + }, + "sendUsageFrom": { + "ignore_above": 256, + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 19a626536f1bd..9479f88085222 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -22,8 +22,6 @@ import { WatcherPageProvider } from './watcher_page'; // @ts-ignore not ts yet import { ReportingPageProvider } from './reporting_page'; // @ts-ignore not ts yet -import { SpaceSelectorPageProvider } from './space_selector_page'; -// @ts-ignore not ts yet import { AccountSettingProvider } from './accountsetting_page'; import { InfraHomePageProvider } from './infra_home_page'; import { InfraLogsPageProvider } from './infra_logs_page'; @@ -46,6 +44,7 @@ import { CopySavedObjectsToSpacePageProvider } from './copy_saved_objects_to_spa import { LensPageProvider } from './lens_page'; import { InfraMetricExplorerProvider } from './infra_metric_explorer'; import { RoleMappingsPageProvider } from './role_mappings_page'; +import { SpaceSelectorPageProvider } from './space_selector_page'; import { EndpointPageProvider } from './endpoint_page'; // just like services, PageObjects are defined as a map of diff --git a/x-pack/test/functional/page_objects/space_selector_page.js b/x-pack/test/functional/page_objects/space_selector_page.ts similarity index 86% rename from x-pack/test/functional/page_objects/space_selector_page.js rename to x-pack/test/functional/page_objects/space_selector_page.ts index ad0f48bdd50bf..74f53a1cf551f 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.js +++ b/x-pack/test/functional/page_objects/space_selector_page.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; -export function SpaceSelectorPageProvider({ getService, getPageObjects }) { +export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); const testSubjects = getService('testSubjects'); @@ -19,7 +20,7 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }) { log.debug('SpaceSelectorPage:initTests'); } - async clickSpaceCard(spaceId) { + async clickSpaceCard(spaceId: string) { return await retry.try(async () => { log.info(`SpaceSelectorPage:clickSpaceCard(${spaceId})`); await testSubjects.click(`space-card-${spaceId}`); @@ -27,11 +28,11 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }) { }); } - async expectHomePage(spaceId) { + async expectHomePage(spaceId: string) { return await this.expectRoute(spaceId, `/app/kibana#/home`); } - async expectRoute(spaceId, route) { + async expectRoute(spaceId: string, route: string) { return await retry.try(async () => { log.debug(`expectRoute(${spaceId}, ${route})`); await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); @@ -49,7 +50,7 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }) { return await testSubjects.click('spacesNavSelector'); } - async clickSpaceAvatar(spaceId) { + async clickSpaceAvatar(spaceId: string) { return await retry.try(async () => { log.info(`SpaceSelectorPage:clickSpaceAvatar(${spaceId})`); await testSubjects.click(`space-avatar-${spaceId}`); diff --git a/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts b/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts index 95a4341e8a8d0..4d36db88f68ab 100644 --- a/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts +++ b/x-pack/test/functional/services/machine_learning/data_frame_analytics.ts @@ -44,6 +44,15 @@ export function MachineLearningDataFrameAnalyticsProvider( await testSubjects.existOrFail('mlDFAnalyticsRegressionExplorationTablePanel'); }, + async assertClassificationEvaluatePanelElementsExists() { + await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationEvaluatePanel'); + await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationConfusionMatrix'); + }, + + async assertClassificationTablePanelExists() { + await testSubjects.existOrFail('mlDFAnalyticsClassificationExplorationTablePanel'); + }, + async assertOutlierTablePanelExists() { await testSubjects.existOrFail('mlDFAnalyticsOutlierExplorationTablePanel'); }, diff --git a/x-pack/test/saved_object_api_integration/common/lib/space_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/space_test_utils.ts index d858177dc62ca..1619d77761c84 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/space_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/space_test_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export function getUrlPrefix(spaceId: string) { return spaceId && spaceId !== DEFAULT_SPACE_ID ? `/s/${spaceId}` : ``; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index d719543fa3807..b6f1bb956d72d 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index ab7babff8dead..9c5cc375502d1 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts index e0cc1498d71ca..d14c5ccbd1d0e 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/create.ts b/x-pack/test/saved_object_api_integration/common/suites/create.ts index 3ee0548b707bc..29960c513d40f 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/create.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/delete.ts b/x-pack/test/saved_object_api_integration/common/suites/delete.ts index 9581a2b3983ef..d96ae5446d732 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/delete.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/delete.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index 114a1fe53ccd6..4a56d18342dc9 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/find.ts b/x-pack/test/saved_object_api_integration/common/suites/find.ts index 6799f0ec63846..f270fc8f4db05 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/find.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/find.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/get.ts b/x-pack/test/saved_object_api_integration/common/suites/get.ts index 39bfc5df4d6e3..c98209ca1e105 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/get.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/import.ts b/x-pack/test/saved_object_api_integration/common/suites/import.ts index 8e4ef61cf3c12..f6723c912f82e 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/import.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/import.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts index 8ae3a1258ab3a..1b538b9b1b65d 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index cd291c53c5f34..d6b7602c0114a 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { getIdPrefix, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts index 9206e48afe9a4..f233bc1d11d7c 100644 --- a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts +++ b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export function getUrlPrefix(spaceId?: string) { return spaceId && spaceId !== DEFAULT_SPACE_ID ? `/s/${spaceId}` : ``; diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 20b4d024803d7..071067ffa85cb 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import { EsArchiver } from 'src/es_archiver'; import { SavedObject } from 'src/core/server'; -import { DEFAULT_SPACE_ID } from '../../../../legacy/plugins/spaces/common/constants'; +import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; import { CopyResponse } from '../../../../plugins/spaces/server/lib/copy_to_spaces'; import { getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 7d2933f9d9238..978271166cc05 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -35,9 +35,6 @@ "test_utils/*": [ "x-pack/test_utils/*" ], - "monitoring/common/*": [ - "x-pack/monitoring/common/*" - ], "plugins/*": ["src/legacy/core_plugins/*/public/"], "fixtures/*": ["src/fixtures/*"] }, @@ -46,4 +43,4 @@ "jest" ] } -} +} \ No newline at end of file