diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md index 960d610b589b8..e01a5e9da50d5 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md @@ -17,6 +17,7 @@ core: { i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; + savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md index e12121e0e3ebb..68a1c27b11836 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.md @@ -16,5 +16,5 @@ export interface AppMountContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | +| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
savedObjects: SavedObjectsStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index dba0ad8c8560c..25eebf1c06d01 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -23,6 +23,7 @@ export interface HttpServiceSetup | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | | [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | | [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | ## Example diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md new file mode 100644 index 0000000000000..9f0eaae8830e1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.registeronpreresponse.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) + +## HttpServiceSetup.registerOnPreResponse property + +To define custom logic to perform for the server response. + +Signature: + +```typescript +registerOnPreResponse: (handler: OnPreResponseHandler) => void; +``` + +## Remarks + +Doesn't provide the whole response object. Supports extending response with custom headers. See [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md). + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 9144742c9bb73..fceabd1237665 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -77,6 +77,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | | [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | +| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | +| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | | [PackageInfo](./kibana-plugin-server.packageinfo.md) | | | [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. | @@ -173,6 +176,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | | [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | | [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md new file mode 100644 index 0000000000000..8736020daf063 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.headers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) > [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) + +## OnPreResponseExtensions.headers property + +additional headers to attach to the response + +Signature: + +```typescript +headers?: ResponseHeaders; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md new file mode 100644 index 0000000000000..e5aa624c39909 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseextensions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) + +## OnPreResponseExtensions interface + +Additional data to extend a response. + +Signature: + +```typescript +export interface OnPreResponseExtensions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [headers](./kibana-plugin-server.onpreresponseextensions.headers.md) | ResponseHeaders | additional headers to attach to the response | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md new file mode 100644 index 0000000000000..082de0a9b4aeb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsehandler.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) + +## OnPreResponseHandler type + +See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). + +Signature: + +```typescript +export declare type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md new file mode 100644 index 0000000000000..736b4298037cf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) + +## OnPreResponseInfo interface + +Response status code. + +Signature: + +```typescript +export interface OnPreResponseInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) | number | | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md new file mode 100644 index 0000000000000..4fd4529dc400f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponseinfo.statuscode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) > [statusCode](./kibana-plugin-server.onpreresponseinfo.statuscode.md) + +## OnPreResponseInfo.statusCode property + +Signature: + +```typescript +statusCode: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md new file mode 100644 index 0000000000000..5525f5bf60284 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) + +## OnPreResponseToolkit interface + +A tool set defining an outcome of OnPreAuth interceptor for incoming request. + +Signature: + +```typescript +export interface OnPreResponseToolkit +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) | (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult | To pass request to the next handler | + diff --git a/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md new file mode 100644 index 0000000000000..bfb5827b16b2f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.onpreresponsetoolkit.next.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) > [next](./kibana-plugin-server.onpreresponsetoolkit.next.md) + +## OnPreResponseToolkit.next property + +To pass request to the next handler + +Signature: + +```typescript +next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +``` diff --git a/docs/management/field-formatters/url-formatter.asciidoc b/docs/management/field-formatters/url-formatter.asciidoc index 2cbade4431202..41d4f75603dc6 100644 --- a/docs/management/field-formatters/url-formatter.asciidoc +++ b/docs/management/field-formatters/url-formatter.asciidoc @@ -4,6 +4,8 @@ The `Url` field formatter can take on the following types: * The *Image* type can be used to specify an image directory where a specified image is located. * The *Audio* type can be used to specify an audio directory where a specified audio file is located. +For an *Image* type you can specify width and height attributes. These will be used to set the max width / max height of the image, while keeping the aspect ratio. Image will not be upscaled if it's smaller than the provided size parameters. + You can customize either type of URL field formats with templates. A _URL template_ enables you to add specific values to a partial URL. Use the string `{{value}}` to add the contents of the field to a fixed URL. diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index c983fa2cfe4ba..37fbc6d78c8e4 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -13,11 +13,20 @@ EMS requests are made to the following domains: * vector.maps.elastic.co **Elastic Maps** makes requests directly from the browser to EMS. -To proxy EMS requests through the Kibana server, set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file. + +[float] +=== Connect to Elastic Maps Service from an internal network + +To connect to EMS when your Kibana server and browser are in an internal network: + +. Set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file to proxy EMS requests through the Kibana server. +. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains listed above. + +NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server. [float] -=== Disabling Elastic Maps Service +=== Disable Elastic Maps Service You might experience EMS connection issues if your Kibana server or browser are on a private network or behind a firewall. If this happens, you can disable the EMS connection to avoid unnecessary EMS requests. diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc index 7452f1c4c3d7e..e9085d18185ec 100644 --- a/docs/visualize/most-frequent.asciidoc +++ b/docs/visualize/most-frequent.asciidoc @@ -7,20 +7,19 @@ levels of {es} {ref}/search-aggregations-bucket.html[bucket] aggregations. The most frequently used visualizations include: -* Line, Area and Bar charts +* Line, area, and bar charts * Pie charts -* Data table -* Metric visualization -* Goal and Gauge visualization +* Data tables +* Metric, goals, and gauges * Heat maps -* Tag cloud +* Tag clouds [float] === Configure your visualization -You configure visualizations using the default editor, which is broken into *Metrics* and *Buckets*, and includes a default count +You configure visualizations using the default editor, which is broken into metrics and buckets, and includes a default count metric. Each visualization supports different configurations for what the metrics and buckets -represent. For example, a Bar chart allows you to add an X-axis: +represent. For example, a bar chart allows you to add an X-axis: [role="screenshot"] image::images/add-bucket.png["",height=478] diff --git a/package.json b/package.json index c81cf17e2c7d3..cf36d4ce884ac 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,8 @@ "**/image-diff/gm/debug": "^2.6.9", "**/react-dom": "^16.12.0", "**/react-test-renderer": "^16.12.0", - "**/deepmerge": "^4.2.2" + "**/deepmerge": "^4.2.2", + "**/serialize-javascript": "^2.1.1" }, "workspaces": { "packages": [ @@ -165,6 +166,7 @@ "encode-uri-query": "1.0.1", "execa": "^3.2.0", "expiry-js": "0.1.7", + "fast-deep-equal": "^3.1.1", "file-loader": "4.2.0", "font-awesome": "4.7.0", "getos": "^3.1.0", @@ -228,6 +230,7 @@ "react-resize-detector": "^4.2.0", "react-router-dom": "^4.3.1", "react-sizeme": "^2.3.6", + "react-use": "^13.10.2", "reactcss": "1.2.3", "redux": "4.0.0", "redux-actions": "2.2.1", diff --git a/packages/kbn-utility-types/README.md b/packages/kbn-utility-types/README.md index ff6c7c7268a15..9707ff5a1ed9c 100644 --- a/packages/kbn-utility-types/README.md +++ b/packages/kbn-utility-types/README.md @@ -18,7 +18,9 @@ type B = UnwrapPromise; // string ## Reference -- `UnwrapPromise` — Returns wrapped type of a promise. -- `UnwrapObservable` — Returns wrapped type of an observable. -- `ShallowPromise` — Same as `Promise` type, but it flat maps the wrapped type. +- `Ensure` — Makes sure `T` is of type `X`. - `ObservableLike` — Minimal interface for an object resembling an `Observable`. +- `RecursiveReadonly` — Like `Readonly`, but freezes object recursively. +- `ShallowPromise` — Same as `Promise` type, but it flat maps the wrapped type. +- `UnwrapObservable` — Returns wrapped type of an observable. +- `UnwrapPromise` — Returns wrapped type of a promise. diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index f17890528bfd2..495b5fb374b43 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -42,3 +42,19 @@ export type UnwrapObservable> = T extends Observab * Converts a type to a `Promise`, unless it is already a `Promise`. Useful when proxying the return value of a possibly async function. */ export type ShallowPromise = T extends Promise ? Promise : Promise; + +/** + * Ensures T is of type X. + */ +export type Ensure = T extends X ? T : never; + +// If we define this inside RecursiveReadonly TypeScript complains. +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface RecursiveReadonlyArray extends Array> {} +export type RecursiveReadonly = T extends (...args: any) => any + ? T + : T extends any[] + ? RecursiveReadonlyArray + : T extends object + ? Readonly<{ [K in keyof T]: RecursiveReadonly }> + : T; diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index eb9a66e99f0aa..84b99b034af99 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1225,6 +1225,7 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | | `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | +| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_ diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 6313a27b6b821..a031ab6070413 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -30,6 +30,7 @@ import { OverlayStart } from '../overlays'; import { PluginOpaqueId } from '../plugins'; import { IUiSettingsClient } from '../ui_settings'; import { RecursiveReadonly } from '../../utils'; +import { SavedObjectsStart } from '../saved_objects'; /** @public */ export interface AppBase { @@ -118,6 +119,8 @@ export interface AppMountContext { notifications: NotificationsStart; /** {@link OverlayStart} */ overlays: OverlayStart; + /** {@link SavedObjectsStart} */ + savedObjects: SavedObjectsStart; /** {@link IUiSettingsClient} */ uiSettings: IUiSettingsClient; /** diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 2404459ad1383..4818484b00819 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -255,6 +255,7 @@ export class CoreSystem { i18n, notifications, overlays, + savedObjects, uiSettings, injectedMetadata: pick(injectedMetadata, ['getInjectedVar']), })); diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index fe7c749091b03..5887e7b3e96d0 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -21,10 +21,10 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; import { BasePath } from './base_path_service'; -import { AnonymousPaths } from './anonymous_paths'; export type HttpSetupMock = jest.Mocked & { basePath: BasePath; + anonymousPaths: jest.Mocked; }; const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({ @@ -37,7 +37,10 @@ const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({ delete: jest.fn(), options: jest.fn(), basePath: new BasePath(basePath), - anonymousPaths: new AnonymousPaths(new BasePath(basePath)), + anonymousPaths: { + register: jest.fn(), + isAnonymous: jest.fn(), + }, addLoadingCount: jest.fn(), getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)), stop: jest.fn(), diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 695f0454f8b65..644df259b8e24 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -18,7 +18,7 @@ */ import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; -import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext, NotificationsSetup } from '.'; +import { CoreContext, PluginInitializerContext } from '.'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -42,7 +42,7 @@ export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; function createCoreSetupMock({ basePath = '' } = {}) { - const mock: MockedKeys & { notifications: MockedKeys } = { + const mock = { application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), @@ -58,7 +58,7 @@ function createCoreSetupMock({ basePath = '' } = {}) { } function createCoreStartMock({ basePath = '' } = {}) { - const mock: MockedKeys & { notifications: MockedKeys } = { + const mock = { application: applicationServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 2dbf4d54efef6..6fbc7324ce393 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -63,6 +63,7 @@ export interface AppMountContext { i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; + savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index f77184fb79ab6..244b3cca60f31 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { Request, Server } from 'hapi'; +import { Server } from 'hapi'; import url from 'url'; import { Logger, LoggerFactory } from '../logging'; @@ -26,8 +25,9 @@ import { createServer, getListenerOptions, getServerOptions } from './http_tools import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth'; +import { adoptToHapiOnPreResponseFormat, OnPreResponseHandler } from './lifecycle/on_pre_response'; -import { ResponseHeaders, IRouter } from './router'; +import { IRouter } from './router'; import { SessionStorageCookieOptions, createCookieSessionStorageFactory, @@ -50,6 +50,7 @@ export interface HttpServerSetup { registerAuth: HttpServiceSetup['registerAuth']; registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; + registerOnPreResponse: HttpServiceSetup['registerOnPreResponse']; isTlsEnabled: HttpServiceSetup['isTlsEnabled']; auth: { get: GetAuthState; @@ -103,6 +104,7 @@ export class HttpServer { registerRouter: this.registerRouter.bind(this), registerOnPreAuth: this.registerOnPreAuth.bind(this), registerOnPostAuth: this.registerOnPostAuth.bind(this), + registerOnPreResponse: this.registerOnPreResponse.bind(this), createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => this.createCookieSessionStorageFactory(cookieOptions, config.basePath), registerAuth: this.registerAuth.bind(this), @@ -232,6 +234,14 @@ export class HttpServer { this.server.ext('onRequest', adoptToHapiOnPreAuthFormat(fn, this.log)); } + private registerOnPreResponse(fn: OnPreResponseHandler) { + if (this.server === undefined) { + throw new Error('Server is not created yet'); + } + + this.server.ext('onPreResponse', adoptToHapiOnPreResponseFormat(fn, this.log)); + } + private async createCookieSessionStorageFactory( cookieOptions: SessionStorageCookieOptions, basePath?: string @@ -289,39 +299,9 @@ export class HttpServer { // https://github.com/hapijs/hapi/blob/master/API.md#-serverauthdefaultoptions this.server.auth.default('session'); - this.server.ext('onPreResponse', (request, t) => { + this.registerOnPreResponse((request, preResponseInfo, t) => { const authResponseHeaders = this.authResponseHeaders.get(request); - this.extendResponseWithHeaders(request, authResponseHeaders); - return t.continue; - }); - } - - private extendResponseWithHeaders(request: Request, headers?: ResponseHeaders) { - const response = request.response; - if (!headers || !response) return; - - if (response instanceof Error) { - this.findHeadersIntersection(response.output.headers, headers); - // hapi wraps all error response in Boom object internally - response.output.headers = { - ...response.output.headers, - ...(headers as any), // hapi types don't specify string[] as valid value - }; - } else { - for (const [headerName, headerValue] of Object.entries(headers)) { - this.findHeadersIntersection(response.headers, headers); - response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value - } - } - } - - // NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object. - // any headers added by hapi internally, like `content-type`, `content-length`, etc. do not present here. - private findHeadersIntersection(responseHeaders: ResponseHeaders, headers: ResponseHeaders) { - Object.keys(headers).forEach(headerName => { - if (responseHeaders[headerName] !== undefined) { - this.log.warn(`Server rewrites a response header [${headerName}].`); - } + return t.next({ headers: authResponseHeaders }); }); } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index c7f6cdb2bb422..444aa04171dbd 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -25,6 +25,7 @@ import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; import { AuthToolkit } from './lifecycle/auth'; import { sessionStorageMock } from './cookie_session_storage.mocks'; import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; +import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; export type HttpServiceSetupMock = jest.Mocked & { basePath: jest.Mocked; @@ -51,6 +52,7 @@ const createSetupContractMock = () => { registerAuth: jest.fn(), registerOnPostAuth: jest.fn(), registerRouteHandlerContext: jest.fn(), + registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), auth: { @@ -92,12 +94,17 @@ const createAuthToolkitMock = (): jest.Mocked => ({ authenticated: jest.fn(), }); +const createOnPreResponseToolkitMock = (): jest.Mocked => ({ + next: jest.fn(), +}); + export const httpServiceMock = { create: createHttpServiceMock, createBasePath: createBasePathMock, createSetupContract: createSetupContractMock, createOnPreAuthToolkit: createOnPreAuthToolkitMock, createOnPostAuthToolkit: createOnPostAuthToolkitMock, + createOnPreResponseToolkit: createOnPreResponseToolkitMock, createAuthToolkit: createAuthToolkitMock, createRouter: mockRouter.create, }; diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index f9a3a91ec18ad..21de3945f1044 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -64,6 +64,12 @@ export { AuthResultType, } from './lifecycle/auth'; export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth'; +export { + OnPreResponseHandler, + OnPreResponseToolkit, + OnPreResponseExtensions, + OnPreResponseInfo, +} from './lifecycle/on_pre_response'; export { SessionStorageFactory, SessionStorage } from './session_storage'; export { SessionStorageCookieOptions, diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 2a32db77377a4..b16352838fad1 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -161,7 +161,7 @@ describe('OnPreAuth', () => { expect(result.header['www-authenticate']).toBe('challenge'); }); - it("doesn't expose error details if interceptor throws", async () => { + it('does not expose error details if interceptor throws', async () => { const { registerOnPreAuth, server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); @@ -734,7 +734,7 @@ describe('Auth', () => { expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ - "Server rewrites a response header [www-authenticate].", + "onPreResponseHandler rewrote a response header [www-authenticate].", ], ] `); @@ -769,7 +769,7 @@ describe('Auth', () => { expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` Array [ Array [ - "Server rewrites a response header [www-authenticate].", + "onPreResponseHandler rewrote a response header [www-authenticate].", ], ] `); @@ -893,3 +893,164 @@ describe('Auth', () => { .expect(200, { customField: 'undefined' }); }); }); + +describe('OnPreResponse', () => { + it('supports registering response inceptors', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' })); + + const callingOrder: string[] = []; + registerOnPreResponse((req, res, t) => { + callingOrder.push('first'); + return t.next(); + }); + + registerOnPreResponse((req, res, t) => { + callingOrder.push('second'); + return t.next(); + }); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200, 'ok'); + + expect(callingOrder).toEqual(['first', 'second']); + }); + + it('supports additional headers attachments', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => + res.ok({ + headers: { + 'x-my-header': 'foo', + }, + }) + ); + + registerOnPreResponse((req, res, t) => + t.next({ + headers: { + 'x-kibana-header': 'value', + }, + }) + ); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(result.header['x-kibana-header']).toBe('value'); + expect(result.header['x-my-header']).toBe('foo'); + }); + + it('logs a warning if interceptor rewrites response header', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => + res.ok({ + headers: { 'x-kibana-header': 'value' }, + }) + ); + registerOnPreResponse((req, res, t) => + t.next({ + headers: { 'x-kibana-header': 'value' }, + }) + ); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200); + + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "onPreResponseHandler rewrote a response header [x-kibana-header].", + ], + ] + `); + }); + + it("doesn't expose error details if interceptor throws", async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok(undefined)); + registerOnPreResponse((req, res, t) => { + throw new Error('reason'); + }); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(500); + + expect(result.body.message).toBe('An internal server error occurred.'); + expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: reason], + ], + ] + `); + }); + + it('returns internal error if interceptor returns unexpected result', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); + registerOnPreResponse((req, res, t) => ({} as any)); + await server.start(); + + const result = await supertest(innerServer.listener) + .get('/') + .expect(500); + + expect(result.body.message).toBe('An internal server error occurred.'); + expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: [object Object].], + ], + ] + `); + }); + + it('cannot change response statusCode', async () => { + const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup( + setupDeps + ); + const router = createRouter('/'); + + registerOnPreResponse((req, res, t) => { + res.statusCode = 500; + return t.next(); + }); + + router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'ok' })); + + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect(200); + }); +}); diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts new file mode 100644 index 0000000000000..45d7478df9805 --- /dev/null +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -0,0 +1,155 @@ +/* + * 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 { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import Boom from 'boom'; +import { Logger } from '../../logging'; + +import { HapiResponseAdapter, KibanaRequest, ResponseHeaders } from '../router'; + +enum ResultType { + next = 'next', +} + +interface Next { + type: ResultType.next; + headers?: ResponseHeaders; +} + +/** + * @internal + */ +type OnPreResponseResult = Next; + +/** + * Additional data to extend a response. + * @public + */ +export interface OnPreResponseExtensions { + /** additional headers to attach to the response */ + headers?: ResponseHeaders; +} + +/** + * Response status code. + * @public + */ +export interface OnPreResponseInfo { + statusCode: number; +} + +const preResponseResult = { + next(responseExtensions?: OnPreResponseExtensions): OnPreResponseResult { + return { type: ResultType.next, headers: responseExtensions?.headers }; + }, + isNext(result: OnPreResponseResult): result is Next { + return result && result.type === ResultType.next; + }, +}; + +/** + * A tool set defining an outcome of OnPreAuth interceptor for incoming request. + * @public + */ +export interface OnPreResponseToolkit { + /** To pass request to the next handler */ + next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +} + +const toolkit: OnPreResponseToolkit = { + next: preResponseResult.next, +}; + +/** + * See {@link OnPreAuthToolkit}. + * @public + */ +export type OnPreResponseHandler = ( + request: KibanaRequest, + preResponse: OnPreResponseInfo, + toolkit: OnPreResponseToolkit +) => OnPreResponseResult | Promise; + +/** + * @public + * Adopt custom request interceptor to Hapi lifecycle system. + * @param fn - an extension point allowing to perform custom logic for + * incoming HTTP requests. + */ +export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Logger) { + return async function interceptPreResponse( + request: Request, + responseToolkit: HapiResponseToolkit + ): Promise { + const response = request.response; + + try { + if (response) { + const statusCode: number = isBoom(response) + ? response.output.statusCode + : response.statusCode; + + const result = await fn(KibanaRequest.from(request), { statusCode }, toolkit); + if (!preResponseResult.isNext(result)) { + throw new Error( + `Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: ${result}.` + ); + } + if (result.headers) { + if (isBoom(response)) { + findHeadersIntersection(response.output.headers, result.headers, log); + // hapi wraps all error response in Boom object internally + response.output.headers = { + ...response.output.headers, + ...(result.headers as any), // hapi types don't specify string[] as valid value + }; + } else { + for (const [headerName, headerValue] of Object.entries(result.headers)) { + findHeadersIntersection(response.headers, result.headers, log); + response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value + } + } + } + } + } catch (error) { + log.error(error); + const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit); + return hapiResponseAdapter.toInternalError(); + } + return responseToolkit.continue; + }; +} + +function isBoom(response: any): response is Boom { + return response instanceof Boom; +} + +// NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object. +// any headers added by hapi internally, like `content-type`, `content-length`, etc. are not present here. +function findHeadersIntersection( + responseHeaders: ResponseHeaders, + headers: ResponseHeaders, + log: Logger +) { + Object.keys(headers).forEach(headerName => { + if (responseHeaders[headerName] !== undefined) { + log.warn(`onPreResponseHandler rewrote a response header [${headerName}].`); + } + }); +} diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 2c3dfedd1d181..94c1982a18c0a 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -24,6 +24,7 @@ import { SessionStorageFactory } from './session_storage'; import { AuthenticationHandler } from './lifecycle/auth'; import { OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { OnPostAuthHandler } from './lifecycle/on_post_auth'; +import { OnPreResponseHandler } from './lifecycle/on_pre_response'; import { IBasePath } from './base_path_service'; import { PluginOpaqueId, RequestHandlerContext } from '..'; @@ -163,6 +164,18 @@ export interface HttpServiceSetup { */ registerOnPostAuth: (handler: OnPostAuthHandler) => void; + /** + * To define custom logic to perform for the server response. + * + * @remarks + * Doesn't provide the whole response object. + * Supports extending response with custom headers. + * See {@link OnPreResponseHandler}. + * + * @param handler {@link OnPreResponseHandler} - function to call. + */ + registerOnPreResponse: (handler: OnPreResponseHandler) => void; + /** * Access or manipulate the Kibana base path * See {@link IBasePath}. diff --git a/src/core/server/index.ts b/src/core/server/index.ts index efff85142c3e4..57156322e2849 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -105,6 +105,10 @@ export { OnPreAuthToolkit, OnPostAuthHandler, OnPostAuthToolkit, + OnPreResponseHandler, + OnPreResponseToolkit, + OnPreResponseExtensions, + OnPreResponseInfo, RedirectResponseOptions, RequestHandler, RequestHandlerContextContainer, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 5d111884144c1..fcf0c45c17db8 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -270,6 +270,7 @@ export class LegacyService implements CoreService { registerOnPreAuth: setupDeps.core.http.registerOnPreAuth, registerAuth: setupDeps.core.http.registerAuth, registerOnPostAuth: setupDeps.core.http.registerOnPostAuth, + registerOnPreResponse: setupDeps.core.http.registerOnPreResponse, basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 8f864dda6b9f3..c07caaa04ba52 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -90,6 +90,7 @@ function createCoreSetupMock() { registerOnPreAuth: httpService.registerOnPreAuth, registerAuth: httpService.registerAuth, registerOnPostAuth: httpService.registerOnPostAuth, + registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index dfd1052bbec75..6829784e6e0a1 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -159,6 +159,7 @@ export function createPluginSetupContext( registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, + registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7e1226aa7238b..c855e04e420f7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -697,6 +697,7 @@ export interface HttpServiceSetup { registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; registerOnPreAuth: (handler: OnPreAuthHandler) => void; + registerOnPreResponse: (handler: OnPreResponseHandler) => void; registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; } @@ -976,6 +977,27 @@ export interface OnPreAuthToolkit { rewriteUrl: (url: string) => OnPreAuthResult; } +// @public +export interface OnPreResponseExtensions { + headers?: ResponseHeaders; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; + +// @public +export interface OnPreResponseInfo { + // (undocumented) + statusCode: number; +} + +// @public +export interface OnPreResponseToolkit { + next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +} + // @public (undocumented) export interface PackageInfo { // (undocumented) diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index c65ae3a0ec7b9..825b51af96b95 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -32,7 +32,7 @@ import { applyFiltersPopover, changeTimeFilter, extractTimeFilter, - IndexPatternsStart, + IndexPatternsContract, } from '../../../../../../plugins/data/public'; export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION'; @@ -50,7 +50,7 @@ export function createFilterAction( overlays: CoreStart['overlays'], filterManager: FilterManager, timeFilter: TimefilterContract, - indexPatternsService: IndexPatternsStart + indexPatternsService: IndexPatternsContract ): IAction { return createAction({ type: GLOBAL_APPLY_FILTER_ACTION, @@ -75,7 +75,7 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { - return indexPatternsService.indexPatterns.get(filter.meta.index!); + return indexPatternsService.get(filter.meta.index!); }) ); diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 9fdc41dfd58e5..481349463fc56 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -18,7 +18,7 @@ */ // /// Define plugin function -import { DataPlugin as Plugin, DataSetup, DataStart } from './plugin'; +import { DataPlugin as Plugin, DataStart } from './plugin'; export function plugin() { return new Plugin(); @@ -27,15 +27,9 @@ export function plugin() { // /// Export types & static code /** @public types */ -export { DataSetup, DataStart }; +export { DataStart }; -export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, -} from './index_patterns'; +export { Field, FieldType, IFieldList, IndexPattern } from './index_patterns'; export { SearchBar, SearchBarProps } from './search'; export { SavedQueryAttributes, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index.ts b/src/legacy/core_plugins/data/public/index_patterns/index.ts index 74981165f3e47..96995c10e5469 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IFieldType, IIndexPattern, indexPatterns } from '../../../../../plugins/data/public'; +import { IFieldType, indexPatterns } from '../../../../../plugins/data/public'; const getFromSavedObject = indexPatterns.getFromSavedObject; const getRoutes = indexPatterns.getRoutes; @@ -25,13 +25,9 @@ const flattenHitWrapper = indexPatterns.flattenHitWrapper; export { getFromSavedObject, getRoutes, flattenHitWrapper }; export { IFieldType as FieldType }; -export { IIndexPattern as StaticIndexPattern }; export { Field, - FieldListInterface, + IFieldList, IndexPattern, - IndexPatterns, - IndexPatternsStart, - IndexPatternsSetup, - IndexPatternsService, + IndexPatternsContract, } from '../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 6cce91a5a25b5..2a7bd5476f0a5 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -19,7 +19,6 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { createSearchBar, StatetfulSearchBarProps } from './search'; -import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { initLegacyModule } from './shim/legacy_module'; @@ -30,27 +29,20 @@ import { } from './filter/action/apply_filter_action'; import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../plugins/data/public/services'; + export interface DataPluginStartDependencies { data: DataPublicPluginStart; uiActions: IUiActionsSetup; } -/** - * Interface for this plugin's returned `setup` contract. - * - * @public - */ -export interface DataSetup { - indexPatterns: IndexPatternsSetup; -} - /** * Interface for this plugin's returned `start` contract. * * @public */ export interface DataStart { - indexPatterns: IndexPatternsStart; ui: { SearchBar: React.ComponentType; }; @@ -68,34 +60,17 @@ export interface DataStart { * or static code. */ -export class DataPlugin implements Plugin { - private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); - - private setupApi!: DataSetup; +export class DataPlugin implements Plugin { private storage!: IStorageWrapper; - public setup(core: CoreSetup): DataSetup { + public setup(core: CoreSetup) { this.storage = new Storage(window.localStorage); - - this.setupApi = { - indexPatterns: this.indexPatterns.setup(), - }; - - return this.setupApi; } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { - const { uiSettings, http, notifications, savedObjects } = core; - - const indexPatternsService = this.indexPatterns.start({ - uiSettings, - savedObjectsClient: savedObjects.client, - http, - notifications, - fieldFormats: data.fieldFormats, - }); - - initLegacyModule(indexPatternsService.indexPatterns); + // This is required for when Angular code uses Field and FieldList. + setFieldFormats(data.fieldFormats); + initLegacyModule(data.indexPatterns); const SearchBar = createSearchBar({ core, @@ -108,22 +83,18 @@ export class DataPlugin implements Plugin { +export const initLegacyModule = once((indexPatterns: IndexPatternsContract): void => { uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js index 6d4ce31d453cc..d5f58236027a9 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js @@ -23,7 +23,7 @@ import { ControlEditor } from './control_editor'; import { addControl, moveControl, newControl, removeControl, setControl } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { start as data } from '../../../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { EuiButton, @@ -41,7 +41,7 @@ class ControlsTabUi extends Component { } getIndexPattern = async (indexPatternId) => { - return await data.indexPatterns.indexPatterns.get(indexPatternId); + return await npStart.plugins.data.indexPatterns.get(indexPatternId); } onChange = value => this.props.setValue('controls', value) diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js index 86fe6db9b0778..e1dadff4a9ae7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js @@ -28,7 +28,6 @@ import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; -import { start as data } from '../../../../core_plugins/data/public/legacy'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators @@ -173,7 +172,7 @@ class ListControl extends Control { export async function listControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); // dynamic options are only allowed on String fields but the setting defaults to true so it could // be enabled for non-string fields (since UI input is hidden for non-string fields). diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js index b40a9f8e6efd4..c5f6de1c53097 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js @@ -40,27 +40,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } + }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - } } - }), - } - }, - } -})); - chrome.getInjected.mockImplementation((key) => { switch(key) { case 'autocompleteTimeout': return 1000; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js index 2a05a1224aab9..d2b1aff78ac2b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js @@ -26,7 +26,6 @@ import { import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; -import { start as data } from '../../../../core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; const minMaxAgg = (field) => { @@ -103,7 +102,7 @@ class RangeControl extends Control { export async function rangeControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); } catch (err) { // ignore not found error and return control so it can be displayed in disabled state. } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js index 3e6d6a49a1118..eaefcda4140fc 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js @@ -48,27 +48,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - } - } }), - } - }, - } -})); - describe('fetch', () => { const controlParams = { id: '1', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 42ecc0fea5f07..b58325a77e61e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -47,7 +47,6 @@ import { // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { DataStart, IndexPatterns } from '../../../data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -55,8 +54,6 @@ import { SharePluginStart } from '../../../../../plugins/share/public'; export interface RenderDeps { core: LegacyCoreStart; - indexPatterns: IndexPatterns; - dataStart: DataStart; npDataStart: NpDataStart; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index dcedb2a166fcc..04a8e68276fc2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -118,6 +118,7 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { AppStateClass: AppState, config, confirmModal, + indexPatterns: deps.npDataStart.indexPatterns, ...deps, }), }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index b5a6db912bdf0..7eac251a532c7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -39,7 +39,7 @@ import { unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; -import { Query, SavedQuery, IndexPatterns } from '../../../../../plugins/data/public'; +import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; import './dashboard_empty_screen_directive'; @@ -78,7 +78,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; getAppState: any; globalState: State; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; dashboardConfig: any; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js index c7f2adb4b875b..7d60b80b59620 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -119,7 +119,7 @@ export function initDashboardApp(app, deps) { }, resolve: { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -154,7 +154,7 @@ export function initDashboardApp(app, deps) { requireUICapability: 'dashboard.createNew', resolve: { dash: function (redirectWhenMissing, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(); }) @@ -174,7 +174,7 @@ export function initDashboardApp(app, deps) { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(id); }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index cb0980c914983..d2b45fde4727e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -64,7 +64,6 @@ export interface DashboardPluginSetupDependencies { export class DashboardPlugin implements Plugin { private startDependencies: { - dataStart: DataStart; npDataStart: NpDataStart; savedObjectsClient: SavedObjectsClientContract; embeddables: IEmbeddableStart; @@ -84,7 +83,6 @@ export class DashboardPlugin implements Plugin { throw new Error('not started yet'); } const { - dataStart, savedObjectsClient, embeddables, navigation, @@ -96,10 +94,8 @@ export class DashboardPlugin implements Plugin { core: contextCore as LegacyCoreStart, ...angularDependencies, navigation, - dataStart, share, npDataStart, - indexPatterns: dataStart.indexPatterns.indexPatterns, savedObjectsClient, chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, @@ -136,7 +132,6 @@ export class DashboardPlugin implements Plugin { { data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies ) { this.startDependencies = { - dataStart, npDataStart: npData, savedObjectsClient, embeddables, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 674f5616faa30..fd71b7c49e837 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,14 +17,14 @@ * under the License. */ -import { IndexPatterns, IndexPattern, SearchSource } from '../../../kibana_services'; +import { IndexPattern, SearchSource } from '../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { esFilters } from '../../../../../../../../plugins/data/public'; +import { esFilters, IndexPatternsContract } from '../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { @@ -39,7 +39,7 @@ const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map(days => days * DAY_MILLIS); -function fetchContextProvider(indexPatterns: IndexPatterns) { +function fetchContextProvider(indexPatterns: IndexPatternsContract) { return { fetchSurroundingDocs, }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 7abb7166aa902..ed233c08e8d49 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -73,13 +73,12 @@ const { } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; -import { start as dataLP } from '../../../../data/public/legacy'; import { generateFilters } from '../../../../../../plugins/data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; import { registerTimefilterWithGlobalStateFactory } from '../../../../../ui/public/timefilter/setup_router'; import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; -const { savedQueryService } = data.query.savedQueries; +const { getSavedQuery } = data.query.savedQueries; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -123,9 +122,9 @@ app.config($routeProvider => { reloadOnSearch: false, resolve: { savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { - const indexPatterns = dataLP.indexPatterns.indexPatterns; + const indexPatterns = getServices().indexPatterns; const savedSearchId = $route.current.params.id; - return ensureDefaultIndexPattern(core, dataLP, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(core, getServices().data, $rootScope, kbnUrl).then(() => { return Promise.props({ ip: indexPatterns.getCache().then((indexPatternList) => { /** @@ -422,7 +421,7 @@ function discoverController( }; const getFieldCounts = async () => { - // the field counts aren't set until we have the dataLP back, + // the field counts aren't set until we have the data back, // so we wait for the fetch to be done before proceeding if ($scope.fetchStatus === fetchStatuses.COMPLETE) { return $scope.fieldCounts; @@ -578,7 +577,7 @@ function discoverController( if (!angular.equals(sort, currentSort)) $scope.fetch(); }); - // update dataLP source when filters update + // update data source when filters update subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { $scope.filters = filterManager.getFilters(); @@ -588,12 +587,12 @@ function discoverController( } })); - // fetch dataLP when filters fire fetch event + // fetch data when filters fire fetch event subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: $scope.fetch })); - // update dataLP source when hitting forward/back and the query changes + // update data source when hitting forward/back and the query changes $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.indexOf('query') >= 0) $scope.fetch(); }); @@ -633,7 +632,7 @@ function discoverController( let prev = {}; const status = { UNINITIALIZED: 'uninitialized', - LOADING: 'loading', // initial dataLP load + LOADING: 'loading', // initial data load READY: 'ready', // results came back NO_RESULTS: 'none' // no results came back }; @@ -704,7 +703,7 @@ function discoverController( savedSearchTitle: savedSearch.title, } }), - 'dataLP-test-subj': 'saveSearchSuccess', + 'data-test-subj': 'saveSearchSuccess', }); if (savedSearch.id !== $route.current.params.id) { @@ -765,7 +764,7 @@ function discoverController( } else { toastNotifications.addError(error, { title: i18n.translate('kbn.discover.errorLoadingData', { - defaultMessage: 'Error loading dataLP', + defaultMessage: 'Error loading data', }), }); } @@ -813,10 +812,10 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { - defaultMessage: 'dataLP', + defaultMessage: 'data', }); const description = i18n.translate('kbn.discover.inspectorRequestDescription', { - defaultMessage: 'This request queries Elasticsearch to fetch the dataLP for the search.', + defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); @@ -972,7 +971,7 @@ function discoverController( return; } if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { - savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { + getSavedQuery(newSavedQueryId).then((savedQuery) => { $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; updateStateFromSavedQuery(savedQuery); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx index 09ba77c7c4999..e5706b5e3c9bb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx @@ -23,7 +23,7 @@ import { TableHeader } from './table_header'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, FieldType } from '../../../../kibana_services'; +import { IndexPattern, IFieldType } from '../../../../kibana_services'; function getMockIndexPattern() { return ({ @@ -40,7 +40,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: true, - } as FieldType; + } as IFieldType; } else { return { name, @@ -48,7 +48,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: false, - } as FieldType; + } as IFieldType; } }, } as unknown) as IndexPattern; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx index 85308d9c7e03e..7020addb2bc6d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx @@ -19,9 +19,10 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; +import { IndexPatternsContract } from 'src/plugins/data/public'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; -import { IndexPatterns, ElasticSearchHit, getServices } from '../../kibana_services'; +import { ElasticSearchHit, getServices } from '../../kibana_services'; export interface ElasticSearchResult { hits: { @@ -50,7 +51,7 @@ export interface DocProps { /** * IndexPatternService to get a given index pattern by ID */ - indexPatternService: IndexPatterns; + indexPatternService: IndexPatternsContract; /** * Client of ElasticSearch to use for the query */ diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js index cc3d864fd371e..8674726a81076 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js @@ -23,7 +23,7 @@ import { fieldCalculator } from './lib/field_calculator'; import './discover_field'; import './discover_field_search_directive'; import './discover_index_pattern_directive'; -import { FieldList } from '../../kibana_services'; +import { FieldList } from '../../../../../../../plugins/data/public'; import fieldChooserTemplate from './field_chooser.html'; export function createFieldChooserDirective($location, config, $route) { 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 8e2d15ae48a1f..275dfa073fecd 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 @@ -301,7 +301,7 @@ function createElasticSearchModule() { } function createIndexPatternsModule() { - angular.module('discoverIndexPatterns', []).service('indexPatterns', IndexPatterns); + angular.module('discoverIndexPatterns', []).value('indexPatterns', IndexPatterns); } function createPagerFactoryModule() { diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts index 51f1521504be8..a7f849704b5b2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts @@ -27,7 +27,7 @@ import { import * as docViewsRegistry from 'ui/registry/doc_views'; import chromeLegacy from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { FilterManager, TimefilterContract } from 'src/plugins/data/public'; +import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; // @ts-ignore import { StateProvider } from 'ui/state_management/state'; // @ts-ignore @@ -35,8 +35,6 @@ import { createSavedSearchesService } from '../saved_searches/saved_searches'; // @ts-ignore import { createSavedSearchFactory } from '../saved_searches/_saved_search'; import { DiscoverStartPlugins } from '../plugin'; -import { start as legacyData } from '../../../../data/public/legacy'; -import { DataStart, IndexPatterns } from '../../../../data/public'; import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; import { SavedSearch } from '../types'; import { SharePluginStart } from '../../../../../../plugins/share/public'; @@ -46,12 +44,11 @@ export interface DiscoverServices { capabilities: Capabilities; chrome: ChromeStart; core: CoreStart; - data: DataStart; docLinks: DocLinksStart; docViewsRegistry: docViewsRegistry.DocViewsRegistry; eui_utils: EuiUtilsStart; filterManager: FilterManager; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; inspector: unknown; metadata: { branch: string }; share: SharePluginStart; @@ -98,7 +95,7 @@ export async function buildServices(core: CoreStart, plugins: DiscoverStartPlugi docViewsRegistry, eui_utils: plugins.eui_utils, filterManager: plugins.data.query.filterManager, - indexPatterns: legacyData.indexPatterns.indexPatterns, + indexPatterns: plugins.data.indexPatterns, inspector: plugins.inspector, // @ts-ignore metadata: core.injectedMetadata.getLegacyMetadata(), diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 43a0afa83dfe4..d13d0dc868a58 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -67,7 +67,6 @@ export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; // @ts-ignore export { RequestAdapter } from 'ui/inspector/adapters'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { FieldList } from 'ui/index_patterns'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; @@ -82,7 +81,12 @@ export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; // EXPORT types export { Vis } from 'ui/vis'; -export { IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns'; +export { + IndexPatternsContract, + IIndexPattern, + IndexPattern, + IFieldType, +} from '../../../../../plugins/data/public'; export { ElasticSearchHit } from 'ui/registry/doc_views_types'; export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; export { Adapters } from 'ui/inspector/types'; diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index 6494cc79640e1..bd3a0b38ec3f0 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -23,7 +23,6 @@ import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as data } from '../../../data/public/legacy'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; export const trackUiMetric = createUiStatsReporter('Kibana_home'); @@ -74,6 +73,6 @@ let copiedLegacyCatalogue = false; }, }); instance.start(npStart.core, { - data, + data: npStart.plugins.data, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index bb0e7e3611616..fc1747d71d069 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -20,7 +20,7 @@ import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; @@ -31,7 +31,7 @@ export interface LegacyAngularInjectedDependencies { } export interface HomePluginStartDependencies { - data: DataStart; + data: DataPublicPluginStart; } export interface HomePluginSetupDependencies { @@ -58,7 +58,7 @@ export interface HomePluginSetupDependencies { } export class HomePlugin implements Plugin { - private dataStart: DataStart | null = null; + private dataStart: DataPublicPluginStart | null = null; private savedObjectsClient: any = null; setup( @@ -85,7 +85,7 @@ export class HomePlugin implements Plugin { uiSettings: core.uiSettings, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, - indexPatternService: this.dataStart!.indexPatterns.indexPatterns, + indexPatternService: this.dataStart!.indexPatterns, ...angularDependencies, }); const { renderApp } = await import('./render_app'); 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 162fae33ba19e..22c3e372230f5 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 @@ -17,7 +17,6 @@ * under the License. */ -import { SavedObjectNotFound } from '../../../../../../../../plugins/kibana_utils/public'; import { i18n } from '@kbn/i18n'; async function getSavedObject(doc, services) { @@ -255,7 +254,8 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - if (error instanceof SavedObjectNotFound) { + + if (error.constructor.name === 'SavedObjectNotFound') { if (error.savedObjectType === 'index-pattern') { conflictedIndexPatterns.push({ obj, doc: searchDoc }); } else { @@ -275,7 +275,7 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - const isIndexPatternNotFound = error instanceof SavedObjectNotFound && + const isIndexPatternNotFound = error.constructor.name === 'SavedObjectNotFound' && error.savedObjectType === 'index-pattern'; if (isIndexPatternNotFound && obj.savedSearchId) { conflictedSavedObjectsLinkedToSavedSearches.push(obj); diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index c985e1a00655f..9a02f5943116d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -54,7 +54,6 @@ const { chrome, chromeLegacy, npData, - data, docTitle, FilterBarQueryFilterProvider, getBasePath, @@ -86,7 +85,7 @@ uiRoutes ); } - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + return ensureDefaultIndexPattern(core, npData, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) .then(savedVis => { if (savedVis.vis.type.setup) { return savedVis.vis.type.setup(savedVis) @@ -105,7 +104,7 @@ uiRoutes k7Breadcrumbs: getEditBreadcrumbs, resolve: { savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(core, npData, $rootScope, kbnUrl) .then(() => savedVisualizations.get($route.current.params.id)) .then((savedVis) => { chrome.recentlyAccessed.add( diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 79acc189b6a29..e5a723a99eafd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -313,7 +313,6 @@ export class VisualizeEmbeddable extends Embeddable false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -67,7 +67,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) }, }); 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 5ae58204a8cf3..a2f98b8c64e53 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 @@ -39,7 +39,7 @@ import { setup as visualizationsSetup } from '../../visualizations/public/np_rea // eslint-disable-next-line import { stubFields } from '../../../../plugins/data/public/stubs'; // eslint-disable-next-line -import { setFieldFormats } from '../../../../plugins/data/public/index_patterns/services'; +import { setFieldFormats } from '../../../../plugins/data/public/services'; interface TableVisScope extends IScope { [key: string]: any; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js index 7aa60bb0cc469..1a5c4713ef7e5 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js @@ -22,7 +22,6 @@ import { toastNotifications } from 'ui/notify'; import { VegaView } from './vega_view/vega_view'; import { VegaMapView } from './vega_view/vega_map_view'; import { timefilter } from 'ui/timefilter'; -import { start as data } from '../../../core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; import { findIndexPatternByTitle } from '../../data/public/index_patterns'; @@ -50,7 +49,7 @@ export const createVegaVisualization = ({ serviceSettings }) => class VegaVisual })); } } else { - idxObj = await data.indexPatterns.indexPatterns.getDefault(); + idxObj = await npStart.plugins.data.indexPatterns.getDefault(); if (!idxObj) { throw new Error(i18n.translate('visTypeVega.visualization.unableToFindDefaultIndexErrorMessage', { defaultMessage: 'Unable to find default index', diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts index 7076cdd8e23e3..ddf1b11945422 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts @@ -24,7 +24,7 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { PersistedState } from 'ui/persisted_state'; import { VisResponseValue } from 'src/plugins/visualizations/public'; import { ExpressionFunction, Render } from 'src/plugins/expressions/public'; -import { start as data } from '../../../data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { start as visualizations } from '../np_ready/public/legacy'; interface Arguments { @@ -92,7 +92,7 @@ export const visualization = (): ExpressionFunctionVisualization => ({ async fn(context, args, handlers) { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private') as any; - const { indexPatterns } = data.indexPatterns; + const indexPatterns = npStart.plugins.data.indexPatterns; const queryFilter = Private(FilterBarQueryFilterProvider); const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts index ab60f5553f79b..4fda86bd1379f 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.ts @@ -25,7 +25,7 @@ import { FieldParamEditor } from '../../vis/editors/default/controls/field'; import { BaseParamType } from './base'; import { toastNotifications } from '../../notify'; import { propFilter } from '../filter'; -import { Field, FieldListInterface } from '../../index_patterns'; +import { Field, IFieldList } from '../../index_patterns'; const filterByType = propFilter('type'); @@ -111,7 +111,7 @@ export class FieldParamType extends BaseParamType { /** * filter the fields to the available ones */ - getAvailableFields = (fields: FieldListInterface) => { + getAvailableFields = (fields: IFieldList) => { const filteredFields = fields.filter((field: Field) => { const { onlyAggregatable, scriptable, filterFieldTypes } = this; diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 6f3d39299f970..0cb1cf897b166 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -17,18 +17,9 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; import { indexPatterns as npIndexPatterns } from '../../../../../plugins/data/public'; -const dataSetup = dataPluginMock.createSetupContract(); - -// mocks for stateful code -export const { indexPatterns } = dataSetup.indexPatterns!; - export const flattenHitWrapper = npIndexPatterns.flattenHitWrapper; -export const formatHitProvider = npIndexPatterns.formatHitProvider; // static code export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; -export { FieldList } from '../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index 237f3137bc9f4..47f6e697c54e9 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -27,23 +27,14 @@ import { indexPatterns } from '../../../../plugins/data/public'; // static code -export { getFromSavedObject, getRoutes } from '../../../core_plugins/data/public'; - export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; export const validateIndexPattern = indexPatterns.validate; export const flattenHitWrapper = indexPatterns.flattenHitWrapper; -export const formatHitProvider = indexPatterns.formatHitProvider; +export const getFromSavedObject = indexPatterns.getFromSavedObject; +export const getRoutes = indexPatterns.getRoutes; // types -export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, -} from '../../../core_plugins/data/public'; - -export { FieldList } from '../../../../plugins/data/public'; +export { Field, FieldType, IFieldList, IndexPattern } from '../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index 98e95865d7325..bb6b9f805b413 100644 --- a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiCallOut } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; -import { DataStart } from '../../../core_plugins/data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; let bannerId: string; let timeoutId: NodeJS.Timeout | undefined; @@ -40,11 +40,11 @@ let timeoutId: NodeJS.Timeout | undefined; */ export async function ensureDefaultIndexPattern( newPlatform: CoreStart, - data: DataStart, + data: DataPublicPluginStart, $rootScope: IRootScopeService, kbnUrl: any ) { - const patterns = await data.indexPatterns.indexPatterns.getIds(); + const patterns = await data.indexPatterns.getIds(); let defaultId = newPlatform.uiSettings.get('defaultIndex'); let defined = !!defaultId; const exists = contains(patterns, defaultId); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js index 9b0398cf8853e..559124e20c10a 100644 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -463,7 +463,7 @@ describe('AggConfig', function () { }); it('returns the field\'s formatter', function () { - expect(vis.aggs.aggs[0].fieldFormatter()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor()); + expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor().toString()); }); it('returns the string format if the field does not have a format', function () { @@ -484,7 +484,7 @@ describe('AggConfig', function () { it('returns the html converter if "html" is passed in', function () { const field = indexPattern.fields.getByName('bytes'); - expect(vis.aggs.aggs[0].fieldFormatter('html')).to.be(field.format.getConverterFor('html')); + expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be(field.format.getConverterFor('html').toString()); }); }); }); diff --git a/src/legacy/ui/public/vis/editors/default/default.html b/src/legacy/ui/public/vis/editors/default/default.html index 2a759815f57f2..60fcbafdb88f5 100644 --- a/src/legacy/ui/public/vis/editors/default/default.html +++ b/src/legacy/ui/public/vis/editors/default/default.html @@ -11,6 +11,7 @@
diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index ae17188de8625..c8c8ac1ffd321 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; import { IndexPattern } from '../index_patterns'; -import { getNotifications, getFieldFormats } from '../services'; +import { getNotifications, getFieldFormats } from '../../services'; import { IFieldType, getKbnFieldType, diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index ff6706cec6c34..03214a8c96427 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -24,14 +24,14 @@ import { Field, FieldSpec } from './field'; type FieldMap = Map; -export interface FieldListInterface extends Array { +export interface IFieldList extends Array { getByName(name: Field['name']): Field | undefined; getByType(type: Field['type']): Field[]; add(field: FieldSpec): void; remove(field: IFieldType): void; } -export class FieldList extends Array implements FieldListInterface { +export class FieldList extends Array implements IFieldList { private byName: FieldMap = new Map(); private groups: Map = new Map(); private indexPattern: IndexPattern; diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index e6978e6d3957d..6f4821c391721 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -42,7 +42,7 @@ export const indexPatterns = { formatHitProvider, }; -export { IndexPatternsService } from './index_patterns_service'; -export { Field, FieldList, FieldListInterface } from './fields'; -export { IndexPattern, IndexPatterns } from './index_patterns'; -export { IndexPatternsStart, IndexPatternsSetup } from './types'; +export { Field, FieldList, IFieldList } from './fields'; + +// TODO: figure out how to replace IndexPatterns in get_inner_angular. +export { IndexPattern, IndexPatterns, IndexPatternsContract } from './index_patterns'; 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 a0a884454a3f0..f56f94fa8c260 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 @@ -26,7 +26,7 @@ import mockLogStashFields from '../../../../../fixtures/logstash_fields'; // @ts-ignore import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { Field } from '../fields'; -import { setNotifications, setFieldFormats } from '../services'; +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 diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 2c93c0aa9dc62..19e465104cf4c 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -32,12 +32,12 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../. import { findByTitle, getRoutes } from '../utils'; import { indexPatterns } from '../'; -import { Field, FieldList, FieldListInterface } from '../fields'; +import { Field, FieldList, IFieldList } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { getNotifications, getFieldFormats } from '../services'; +import { getNotifications, getFieldFormats } from '../../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; @@ -50,7 +50,7 @@ export class IndexPattern implements IIndexPattern { public type?: string; public fieldFormatMap: any; public typeMeta: any; - public fields: FieldListInterface; + public fields: IFieldList; public timeFieldName: string | undefined; public formatHit: any; public formatField: any; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index d6a8e7b20451d..6369a2a3dd5f3 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -146,3 +146,5 @@ export class IndexPatterns { return indexPattern.init(); }; } + +export type IndexPatternsContract = PublicMethodsOf; diff --git a/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts deleted file mode 100644 index d38e9c76430eb..0000000000000 --- a/src/plugins/data/public/index_patterns/index_patterns_service.mock.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. - */ - -import { IndexPatternsService } from './index_patterns_service'; -import { flattenHitWrapper } from './index_patterns'; -import { IndexPatternsSetup } from './types'; - -type IndexPatternsServiceClientContract = PublicMethodsOf; - -const createSetupContractMock = () => { - // Legacy mock - must be removed before migrating to new platform. - // Included here because we only want to call `jest.mock` when somebody creates - // the mock for this contract. - jest.mock('ui/chrome'); - - const setupContract: jest.Mocked = { - FieldList: {} as any, - flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper), - formatHitProvider: jest.fn(), - indexPatterns: jest.fn() as any, - IndexPatternSelect: jest.fn(), - }; - - return setupContract; -}; - -const createMock = () => { - const mocked: jest.Mocked = { - setup: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - - mocked.setup.mockReturnValue(createSetupContractMock()); - return mocked; -}; - -export const indexPatternsServiceMock = { - create: createMock, - createSetupContract: createSetupContractMock, - createStartContract: createSetupContractMock, -}; diff --git a/src/plugins/data/public/index_patterns/index_patterns_service.ts b/src/plugins/data/public/index_patterns/index_patterns_service.ts deleted file mode 100644 index 43ba5082ec479..0000000000000 --- a/src/plugins/data/public/index_patterns/index_patterns_service.ts +++ /dev/null @@ -1,70 +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 { - IUiSettingsClient, - SavedObjectsClientContract, - HttpServiceBase, - NotificationsStart, -} from 'src/core/public'; -import { FieldFormatsStart } from '../field_formats_provider'; -import { setNotifications, setFieldFormats } from './services'; -import { IndexPatterns } from './index_patterns'; - -export interface IndexPatternDependencies { - uiSettings: IUiSettingsClient; - savedObjectsClient: SavedObjectsClientContract; - http: HttpServiceBase; - notifications: NotificationsStart; - fieldFormats: FieldFormatsStart; -} - -/** - * Index Patterns Service - * - * @internal - */ -export class IndexPatternsService { - private setupApi: any; - - public setup() { - this.setupApi = {}; - - return this.setupApi; - } - - public start({ - uiSettings, - savedObjectsClient, - http, - notifications, - fieldFormats, - }: IndexPatternDependencies) { - setNotifications(notifications); - setFieldFormats(fieldFormats); - - return { - indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), - }; - } - - public stop() { - // nothing to do here yet - } -} diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 19c21ab9934ef..058e6c0e2f5c5 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,10 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { FieldFormatRegisty, Plugin, FieldFormatsStart, FieldFormatsSetup } from '.'; +import { + FieldFormatRegisty, + Plugin, + FieldFormatsStart, + FieldFormatsSetup, + IndexPatternsContract, +} from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; -import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; @@ -54,7 +59,6 @@ const createSetupContract = (): Setup => { search: searchSetupMock, fieldFormats: fieldFormatsMock as FieldFormatsSetup, query: querySetupMock, - indexPatterns: indexPatternsServiceMock.createSetupContract(), }; return setupContract; @@ -71,7 +75,7 @@ const createStartContract = (): Start => { ui: { IndexPatternSelect: jest.fn(), }, - indexPatterns: indexPatternsServiceMock.createStartContract(), + indexPatterns: {} as IndexPatternsContract, }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index c018efcad74e6..6ec0413968a55 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -26,10 +26,10 @@ import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; -import { IndexPatternsService } from './index_patterns'; +import { IndexPatterns } from './index_patterns'; +import { setNotifications, setFieldFormats } from './services'; export class DataPublicPlugin implements Plugin { - private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); private readonly autocomplete = new AutocompleteProviderRegister(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; @@ -58,6 +58,10 @@ export class DataPublicPlugin implements Plugin( 'Notifications' diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 5e1d8797ebf08..dd70c5646f708 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -25,26 +25,25 @@ import { ISearchSetup, ISearchStart } from './search'; import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; -import { IndexPatternsStart, IndexPatternsSetup } from './index_patterns'; +import { IndexPatternsContract } from './index_patterns'; export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; search: ISearchSetup; fieldFormats: FieldFormatsSetup; query: QuerySetup; - indexPatterns?: IndexPatternsSetup; } export interface DataPublicPluginStart { autocomplete: AutocompletePublicPluginStart; getSuggestions: IGetSuggestions; + indexPatterns: IndexPatternsContract; search: ISearchStart; fieldFormats: FieldFormatsStart; query: QueryStart; ui: { IndexPatternSelect: React.ComponentType; }; - indexPatterns: IndexPatternsStart; } export * from './autocomplete_provider/types'; diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 58658b6c839a2..80a5ede567054 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -172,13 +172,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -305,14 +299,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -803,13 +792,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -936,14 +919,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -1422,13 +1400,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -1555,14 +1527,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -2050,13 +2017,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -2183,14 +2144,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -2669,13 +2625,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -2802,14 +2752,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", @@ -3297,13 +3242,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -3430,14 +3369,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "anonymousPaths": AnonymousPaths { - "basePath": BasePath { - "basePath": "", - "get": [Function], - "prepend": [Function], - "remove": [Function], - }, - "paths": Set {}, + "anonymousPaths": Object { + "isAnonymous": [MockFunction], + "register": [MockFunction], }, "basePath": BasePath { "basePath": "", diff --git a/src/plugins/kibana_utils/README.md b/src/plugins/kibana_utils/README.md index 61ceea2b18385..5501505dbb7e2 100644 --- a/src/plugins/kibana_utils/README.md +++ b/src/plugins/kibana_utils/README.md @@ -2,4 +2,4 @@ Utilities for building Kibana plugins. -- [Store reactive serializable app state in state containers, `createStore`](./docs/store/README.md). +- [State containers](./docs/state_containers/README.md). diff --git a/src/plugins/kibana_utils/public/store/types.ts b/src/plugins/kibana_utils/demos/demos.test.ts similarity index 51% rename from src/plugins/kibana_utils/public/store/types.ts rename to src/plugins/kibana_utils/demos/demos.test.ts index 952ee07f18baf..4e792ceef117a 100644 --- a/src/plugins/kibana_utils/public/store/types.ts +++ b/src/plugins/kibana_utils/demos/demos.test.ts @@ -17,26 +17,20 @@ * under the License. */ -import { Observable } from 'rxjs'; -import { Store as ReduxStore } from 'redux'; +import { result as counterResult } from './state_containers/counter'; +import { result as todomvcResult } from './state_containers/todomvc'; -export interface AppStore< - State extends {}, - StateMutators extends Mutators> = {} -> { - redux: ReduxStore; - get: () => State; - set: (state: State) => void; - state$: Observable; - createMutators: >(pureMutators: M) => Mutators; - mutators: StateMutators; -} +describe('demos', () => { + describe('state containers', () => { + test('counter demo works', () => { + expect(counterResult).toBe(10); + }); -export type PureMutator = (state: State) => (...args: any[]) => State; -export type Mutator> = (...args: Parameters>) => void; - -export interface PureMutators { - [name: string]: PureMutator; -} - -export type Mutators> = { [K in keyof M]: Mutator }; + test('TodoMVC demo works', () => { + expect(todomvcResult).toEqual([ + { id: 0, text: 'Learning state containers', completed: true }, + { id: 1, text: 'Learning transitions...', completed: true }, + ]); + }); + }); +}); diff --git a/src/plugins/data/public/index_patterns/types.ts b/src/plugins/kibana_utils/demos/state_containers/counter.ts similarity index 67% rename from src/plugins/data/public/index_patterns/types.ts rename to src/plugins/kibana_utils/demos/state_containers/counter.ts index 59e3075bf215d..643763cc4cee9 100644 --- a/src/plugins/data/public/index_patterns/types.ts +++ b/src/plugins/kibana_utils/demos/state_containers/counter.ts @@ -17,7 +17,16 @@ * under the License. */ -import { IndexPatternsService } from './index_patterns_service'; +import { createStateContainer } from '../../public/state_containers'; -export type IndexPatternsSetup = ReturnType; -export type IndexPatternsStart = ReturnType; +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); + +container.transitions.increment(5); +container.transitions.double(); + +console.log(container.get()); // eslint-disable-line + +export const result = container.get(); diff --git a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts new file mode 100644 index 0000000000000..6d0c960e2a5b2 --- /dev/null +++ b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts @@ -0,0 +1,69 @@ +/* + * 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 { createStateContainer, PureTransition } from '../../public/state_containers'; + +export interface TodoItem { + text: string; + completed: boolean; + id: number; +} + +export type TodoState = TodoItem[]; + +export const defaultState: TodoState = [ + { + id: 0, + text: 'Learning state containers', + completed: false, + }, +]; + +export interface TodoActions { + add: PureTransition; + edit: PureTransition; + delete: PureTransition; + complete: PureTransition; + completeAll: PureTransition; + clearCompleted: PureTransition; +} + +export const pureTransitions: TodoActions = { + add: state => todo => [...state, todo], + edit: state => todo => state.map(item => (item.id === todo.id ? { ...item, ...todo } : item)), + delete: state => id => state.filter(item => item.id !== id), + complete: state => id => + state.map(item => (item.id === id ? { ...item, completed: true } : item)), + completeAll: state => () => state.map(item => ({ ...item, completed: true })), + clearCompleted: state => () => state.filter(({ completed }) => !completed), +}; + +const container = createStateContainer(defaultState, pureTransitions); + +container.transitions.add({ + id: 1, + text: 'Learning transitions...', + completed: false, +}); +container.transitions.complete(0); +container.transitions.complete(1); + +console.log(container.get()); // eslint-disable-line + +export const result = container.get(); diff --git a/src/plugins/kibana_utils/docs/state_containers/README.md b/src/plugins/kibana_utils/docs/state_containers/README.md new file mode 100644 index 0000000000000..3b7a8b8bd4621 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/README.md @@ -0,0 +1,50 @@ +# State containers + +State containers are Redux-store-like objects meant to help you manage state in +your services or apps. + +- State containers are strongly typed, you will get TypeScript autocompletion suggestions from + your editor when accessing state, executing transitions and using React helpers. +- State containers can be easily hooked up with your React components. +- State containers can be used without React, too. +- State containers provide you central place where to store state, instead of spreading + state around multiple RxJs observables, which you need to coordinate. With state + container you can always access the latest state snapshot synchronously. +- Unlike Redux, state containers are less verbose, see example below. + + +## Example + +```ts +import { createStateContainer } from 'src/plugins/kibana_utils'; + +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); + +container.transitions.increment(5); +container.transitions.double(); +console.log(container.get()); // 10 +``` + + +## Demos + +See demos [here](../../demos/state_containers/). + +You can run them with + +``` +npx -q ts-node src/plugins/kibana_utils/demos/state_containers/counter.ts +npx -q ts-node src/plugins/kibana_utils/demos/state_containers/todomvc.ts +``` + + +## Reference + +- [Creating a state container](./creation.md). +- [State transitions](./transitions.md). +- [Using with React](./react.md). +- [Using without React`](./no_react.md). +- [Parallels with Redux](./redux.md). diff --git a/src/plugins/kibana_utils/docs/store/creation.md b/src/plugins/kibana_utils/docs/state_containers/creation.md similarity index 54% rename from src/plugins/kibana_utils/docs/store/creation.md rename to src/plugins/kibana_utils/docs/state_containers/creation.md index b0184ad45eb84..66d28bbd8603f 100644 --- a/src/plugins/kibana_utils/docs/store/creation.md +++ b/src/plugins/kibana_utils/docs/state_containers/creation.md @@ -17,7 +17,7 @@ interface MyState { } ``` -Create default state of your *store*. +Create default state of your container. ```ts const defaultState: MyState = { @@ -27,17 +27,12 @@ const defaultState: MyState = { }; ``` -Create your state container, i.e *store*. +Create your a state container. ```ts -import { createStore } from 'kibana-utils'; +import { createStateContainer } from 'src/plugins/kibana_utils'; -const store = createStore(defaultState); -console.log(store.get()); -``` +const container = createStateContainer(defaultState, {}); -> ##### N.B. -> -> State must always be an object `{}`. -> -> You cannot create a store out of an array, e.g ~~`createStore([])`~~. +console.log(container.get()); +``` diff --git a/src/plugins/kibana_utils/docs/store/getters.md b/src/plugins/kibana_utils/docs/state_containers/no_react.md similarity index 83% rename from src/plugins/kibana_utils/docs/store/getters.md rename to src/plugins/kibana_utils/docs/state_containers/no_react.md index 508d0c6ebc18d..7a15483d83b44 100644 --- a/src/plugins/kibana_utils/docs/store/getters.md +++ b/src/plugins/kibana_utils/docs/state_containers/no_react.md @@ -1,4 +1,4 @@ -# Reading state +# Consuming state in non-React setting To read the current `state` of the store use `.get()` method. diff --git a/src/plugins/kibana_utils/docs/state_containers/react.md b/src/plugins/kibana_utils/docs/state_containers/react.md new file mode 100644 index 0000000000000..363fd9253d44f --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react.md @@ -0,0 +1,41 @@ +# React + +`createStateContainerReactHelpers` factory allows you to easily use state containers with React. + + +## Example + + +```ts +import { createStateContainer, createStateContainerReactHelpers } from 'src/plugins/kibana_utils'; + +const container = createStateContainer({}, {}); +export const { + Provider, + Consumer, + context, + useContainer, + useState, + useTransitions, + useSelector, + connect, +} = createStateContainerReactHelpers(); +``` + +Wrap your app with ``. + +```tsx + + + +``` + + +## Reference + +- [`useContainer()`](./react/use_container.md) +- [`useState()`](./react/use_state.md) +- [`useSelector()`](./react/use_selector.md) +- [`useTransitions()`](./react/use_transitions.md) +- [`connect()()`](./react/connect.md) +- [Context](./react/context.md) diff --git a/src/plugins/kibana_utils/docs/state_containers/react/connect.md b/src/plugins/kibana_utils/docs/state_containers/react/connect.md new file mode 100644 index 0000000000000..56b7e0fbc5673 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/connect.md @@ -0,0 +1,22 @@ +# `connect()()` higher order component + +Use `connect()()` higher-order-component to inject props from state into your component. + +```tsx +interface Props { + name: string; + punctuation: '.' | ',' | '!', +} +const Demo: React.FC = ({ name, punctuation }) => +
Hello, {name}{punctuation}
; + +const store = createStateContainer({ userName: 'John' }); +const { Provider, connect } = createStateContainerReactHelpers(store); + +const mapStateToProps = ({ userName }) => ({ name: userName }); +const DemoConnected = connect(mapStateToProps)(Demo); + + + + +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/context.md b/src/plugins/kibana_utils/docs/state_containers/react/context.md new file mode 100644 index 0000000000000..33f084fdfe9d7 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/context.md @@ -0,0 +1,24 @@ +# React context + +`createStateContainerReactHelpers` returns `` and `` components +as well as `context` React context object. + +```ts +export const { + Provider, + Consumer, + context, +} = createStateContainerReactHelpers(); +``` + +`` and `` are just regular React context components. + +```tsx + +
+ {container => +
{JSON.stringify(container.get())}
+ }
+
+
+``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_container.md b/src/plugins/kibana_utils/docs/state_containers/react/use_container.md new file mode 100644 index 0000000000000..5e698edb8529c --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_container.md @@ -0,0 +1,10 @@ +# `useContainer` hook + +`useContainer` React hook will simply return you `container` object from React context. + +```tsx +const Demo = () => { + const store = useContainer(); + return
{store.get().isDarkMode ? '🌑' : '☀️'}
; +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md b/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md new file mode 100644 index 0000000000000..2ecf772fba367 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_selector.md @@ -0,0 +1,20 @@ +# `useSelector()` hook + +With `useSelector` React hook you specify a selector function, which will pick specific +data from the state. *Your component will update only when that specific part of the state changes.* + +```tsx +const selector = state => state.isDarkMode; +const Demo = () => { + const isDarkMode = useSelector(selector); + return
{isDarkMode ? '🌑' : '☀️'}
; +}; +``` + +As an optional second argument for `useSelector` you can provide a `comparator` function, which +compares currently selected value with the previous and your component will re-render only if +`comparator` returns `true`. By default it uses [`fast-deep-equal`](https://github.com/epoberezkin/fast-deep-equal). + +``` +useSelector(selector, comparator?) +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_state.md b/src/plugins/kibana_utils/docs/state_containers/react/use_state.md new file mode 100644 index 0000000000000..5db1d46897aad --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_state.md @@ -0,0 +1,11 @@ +# `useState()` hook + +- `useState` hook returns you directly the state of the container. +- It also forces component to re-render every time state changes. + +```tsx +const Demo = () => { + const { isDarkMode } = useState(); + return
{isDarkMode ? '🌑' : '☀️'}
; +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md b/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md new file mode 100644 index 0000000000000..c6783bf0e0f0a --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/react/use_transitions.md @@ -0,0 +1,17 @@ +# `useTransitions` hook + +Access [state transitions](../transitions.md) by `useTransitions` React hook. + +```tsx +const Demo = () => { + const { isDarkMode } = useState(); + const { setDarkMode } = useTransitions(); + return ( + <> +
{isDarkMode ? '🌑' : '☀️'}
+ + + + ); +}; +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/redux.md b/src/plugins/kibana_utils/docs/state_containers/redux.md new file mode 100644 index 0000000000000..1a60d841a8b75 --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/redux.md @@ -0,0 +1,40 @@ +# Redux + +State containers similar to Redux stores but without the boilerplate. + +State containers expose Redux-like API: + +```js +container.getState() +container.dispatch() +container.replaceReducer() +container.subscribe() +container.addMiddleware() +``` + +State containers have a reducer and every time you execute a state transition it +actually dispatches an "action". For example, this + +```js +container.transitions.increment(25); +``` + +is equivalent to + +```js +container.dispatch({ + type: 'increment', + args: [25], +}); +``` + +Because all transitions happen through `.dispatch()` interface, you can add middleware—similar how you +would do with Redux—to monitor or intercept transitions. + +For example, you can add `redux-logger` middleware to log in console all transitions happening with your store. + +```js +import logger from 'redux-logger'; + +container.addMiddleware(logger); +``` diff --git a/src/plugins/kibana_utils/docs/state_containers/transitions.md b/src/plugins/kibana_utils/docs/state_containers/transitions.md new file mode 100644 index 0000000000000..51d52cdf3daaf --- /dev/null +++ b/src/plugins/kibana_utils/docs/state_containers/transitions.md @@ -0,0 +1,61 @@ +# State transitions + +*State transitions* describe possible state changes over time. Transitions are pure functions which +receive `state` object and other—optional—arguments and must return a new `state` object back. + +```ts +type Transition = (state: State) => (...args) => State; +``` + +Transitions must not mutate `state` object in-place, instead they must return a +shallow copy of it, e.g. `{ ...state }`. Example: + +```ts +const setUiMode: PureTransition = state => uiMode => ({ ...state, uiMode }); +``` + +You provide transitions as a second argument when you create your state container. + +```ts +import { createStateContainer } from 'src/plugins/kibana_utils'; + +const container = createStateContainer(0, { + increment: (cnt: number) => (by: number) => cnt + by, + double: (cnt: number) => () => cnt * 2, +}); +``` + +Now you can execute the transitions by calling them with only optional parameters (`state` is +provided to your transitions automatically). + +```ts +container.transitions.increment(25); +container.transitions.increment(5); +container.state; // 30 +``` + +Your transitions are bound to the container so you can treat each of them as a +standalone function for export. + +```ts +const defaultState = { + uiMode: 'light', +}; + +const container = createStateContainer(defaultState, { + setUiMode: state => uiMode => ({ ...state, uiMode }), + resetUiMode: state => () => ({ ...state, uiMode: defaultState.uiMode }), +}); + +export const { + setUiMode, + resetUiMode +} = container.transitions; +``` + +You can add TypeScript annotations for your transitions as the second generic argument +to `createStateContainer()` function. + +```ts +const container = createStateContainer(defaultState, pureTransitions); +``` diff --git a/src/plugins/kibana_utils/docs/store/README.md b/src/plugins/kibana_utils/docs/store/README.md deleted file mode 100644 index e1cb098fe04ce..0000000000000 --- a/src/plugins/kibana_utils/docs/store/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# State containers - -- State containers for holding serializable state. -- [Each plugin/app that needs runtime state will create a *store* using `store = createStore()`](./creation.md). -- [*Store* can be updated using mutators `mutators = store.createMutators({ ... })`](./mutators.md). -- [*Store* can be connected to React `{Provider, connect} = createContext(store)`](./react.md). -- [In no-React setting *store* is consumed using `store.get()` and `store.state$`](./getters.md). -- [Under-the-hood uses Redux `store.redux`](./redux.md) (but you should never need it explicitly). -- [See idea doc with samples and rationale](https://docs.google.com/document/d/18eitHkcyKSsEHUfUIqFKChc8Pp62Z4gcRxdu903hbA0/edit#heading=h.iaxc9whxifl5). diff --git a/src/plugins/kibana_utils/docs/store/mutators.md b/src/plugins/kibana_utils/docs/store/mutators.md deleted file mode 100644 index 9db1b1bb60b3c..0000000000000 --- a/src/plugins/kibana_utils/docs/store/mutators.md +++ /dev/null @@ -1,70 +0,0 @@ -# Mutators - -State *mutators* are pure functions which receive `state` object and other—optional—arguments -and must return a new `state` object back. - -```ts -type Mutator = (state: State) => (...args) => State; -``` - -Mutator must not mutate `state` object in-place, instead it should return a -shallow copy of it, e.g. `{ ...state }`. - -```ts -const setUiMode: Mutator = state => uiMode => ({ ...state, uiMode }); -``` - -You create mutators using `.createMutator(...)` method. - -```ts -const store = createStore({uiMode: 'light'}); -const mutators = store.createMutators({ - setUiMode: state => uiMode => ({ ...state, uiMode }), -}); -``` - -Now you can use your mutators by calling them with only optional parameters (`state` is -provided to your mutator automatically). - -```ts -mutators.setUiMode('dark'); -``` - -Your mutators are bound to the `store` so you can treat each of them as a -standalone function for export. - -```ts -const { setUiMode, resetUiMode } = store.createMutators({ - setUiMode: state => uiMode => ({ ...state, uiMode }), - resetUiMode: state => () => ({ ...state, uiMode: 'light' }), -}); - -export { - setUiMode, - resetUiMode, -}; -``` - -The mutators you create are also available on the `store` object. - -```ts -const store = createStore({ cnt: 0 }); -store.createMutators({ - add: state => value => ({ ...state, cnt: state.cnt + value }), -}); - -store.mutators.add(5); -store.get(); // { cnt: 5 } -``` - -You can add TypeScript annotations to your `.mutators` property of `store` object. - -```ts -const store = createStore<{ - cnt: number; -}, { - add: (value: number) => void; -}>({ - cnt: 0 -}); -``` diff --git a/src/plugins/kibana_utils/docs/store/react.md b/src/plugins/kibana_utils/docs/store/react.md deleted file mode 100644 index 68a016ed6d3ca..0000000000000 --- a/src/plugins/kibana_utils/docs/store/react.md +++ /dev/null @@ -1,101 +0,0 @@ -# React - -`createContext` factory allows you to easily use state containers with React. - -```ts -import { createStore, createContext } from 'kibana-utils'; - -const store = createStore({}); -const { - Provider, - Consumer, - connect, - context, - useStore, - useState, - useMutators, - useSelector, -} = createContext(store); -``` - -Wrap your app with ``. - -```tsx - - - -``` - -Use `connect()()` higer-order-component to inject props from state into your component. - -```tsx -interface Props { - name: string; - punctuation: '.' | ',' | '!', -} -const Demo: React.FC = ({ name, punctuation }) => -
Hello, {name}{punctuation}
; - -const store = createStore({ userName: 'John' }); -const { Provider, connect } = createContext(store); - -const mapStateToProps = ({ userName }) => ({ name: userName }); -const DemoConnected = connect(mapStateToProps)(Demo); - - - - -``` - -`useStore` React hook will fetch the `store` object from the context. - -```tsx -const Demo = () => { - const store = useStore(); - return
{store.get().isDarkMode ? '🌑' : '☀️'}
; -}; -``` - -If you want your component to always re-render when the state changes use `useState` React hook. - -```tsx -const Demo = () => { - const { isDarkMode } = useState(); - return
{isDarkMode ? '🌑' : '☀️'}
; -}; -``` - -For `useSelector` React hook you specify a selector function, which will pick specific -data from the state. *Your component will update only when that specific part of the state changes.* - -```tsx -const selector = state => state.isDarkMode; -const Demo = () => { - const isDarkMode = useSelector(selector); - return
{isDarkMode ? '🌑' : '☀️'}
; -}; -``` - -As an optional second argument for `useSelector` you can provide a `comparator` function, which -compares currently selected value with the previous and your component will re-render only if -`comparator` returns `true`. By default, it simply uses tripple equals `===` comparison. - -``` -useSelector(selector, comparator?) -``` - -Access state mutators by `useMutators` React hook. - -```tsx -const Demo = () => { - const { isDarkMode } = useState(); - const { setDarkMode } = useMutators(); - return ( - <> -
{isDarkMode ? '🌑' : '☀️'}
- - - - ); -}; -``` diff --git a/src/plugins/kibana_utils/docs/store/redux.md b/src/plugins/kibana_utils/docs/store/redux.md deleted file mode 100644 index 23be76f35b36e..0000000000000 --- a/src/plugins/kibana_utils/docs/store/redux.md +++ /dev/null @@ -1,19 +0,0 @@ -# Redux - -Internally `createStore()` uses Redux to manage the state. When you call `store.get()` -it is actually calling the Redux `.getState()` method. When you execute a mutation -it is actually dispatching a Redux action. - -You can access Redux *store* using `.redux`. - -```ts -store.redux; -``` - -But you should never need it, if you think you do, consult with Kibana App Architecture team. - -We use Redux internally for 3 main reasons: - -- We can reuse `react-redux` library to easily connect state containers to React. -- We can reuse Redux devtools. -- We can reuse battle-tested Redux library and action/reducer paradigm. diff --git a/src/plugins/kibana_utils/public/index.test.ts b/src/plugins/kibana_utils/public/index.test.ts index 0e2a4acf15f04..27c4d6c1c06e9 100644 --- a/src/plugins/kibana_utils/public/index.test.ts +++ b/src/plugins/kibana_utils/public/index.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { createStore, createContext } from '.'; +import { createStateContainer, createStateContainerReactHelpers } from '.'; test('exports store methods', () => { - expect(typeof createStore).toBe('function'); - expect(typeof createContext).toBe('function'); + expect(typeof createStateContainer).toBe('function'); + expect(typeof createStateContainerReactHelpers).toBe('function'); }); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index c5c129eca8fd3..3f5aeebac54d8 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -19,13 +19,12 @@ export * from './core'; export * from './errors'; -export * from './store'; -export * from './parse'; -export * from './resize_checker'; -export * from './render_complete'; -export * from './store'; export * from './errors'; export * from './field_mapping'; +export * from './parse'; +export * from './render_complete'; +export * from './resize_checker'; +export * from './state_containers'; export * from './storage'; export * from './storage/hashed_item_store'; export * from './state_management/state_hash'; diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts new file mode 100644 index 0000000000000..9165181299a90 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts @@ -0,0 +1,303 @@ +/* + * 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 { createStateContainer } from './create_state_container'; + +const create = (state: S, transitions: T = {} as T) => { + const pureTransitions = { + set: () => (newState: S) => newState, + ...transitions, + }; + const store = createStateContainer(state, pureTransitions); + return { store, mutators: store.transitions }; +}; + +test('can create store', () => { + const { store } = create({}); + expect(store).toMatchObject({ + getState: expect.any(Function), + state$: expect.any(Object), + transitions: expect.any(Object), + dispatch: expect.any(Function), + subscribe: expect.any(Function), + replaceReducer: expect.any(Function), + addMiddleware: expect.any(Function), + }); +}); + +test('can set default state', () => { + const defaultState = { + foo: 'bar', + }; + const { store } = create(defaultState); + expect(store.get()).toEqual(defaultState); + expect(store.getState()).toEqual(defaultState); +}); + +test('can set state', () => { + const defaultState = { + foo: 'bar', + }; + const newState = { + foo: 'baz', + }; + const { store, mutators } = create(defaultState); + + mutators.set(newState); + + expect(store.get()).toEqual(newState); + expect(store.getState()).toEqual(newState); +}); + +test('does not shallow merge states', () => { + const defaultState = { + foo: 'bar', + }; + const newState = { + foo2: 'baz', + }; + const { store, mutators } = create(defaultState); + + mutators.set(newState as any); + + expect(store.get()).toEqual(newState); + expect(store.getState()).toEqual(newState); +}); + +test('can subscribe and unsubscribe to state changes', () => { + const { store, mutators } = create({}); + const spy = jest.fn(); + const subscription = store.state$.subscribe(spy); + mutators.set({ a: 1 }); + mutators.set({ a: 2 }); + subscription.unsubscribe(); + mutators.set({ a: 3 }); + + expect(spy).toHaveBeenCalledTimes(2); + expect(spy.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy.mock.calls[1][0]).toEqual({ a: 2 }); +}); + +test('multiple subscribers can subscribe', () => { + const { store, mutators } = create({}); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const subscription1 = store.state$.subscribe(spy1); + const subscription2 = store.state$.subscribe(spy2); + mutators.set({ a: 1 }); + subscription1.unsubscribe(); + mutators.set({ a: 2 }); + subscription2.unsubscribe(); + mutators.set({ a: 3 }); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(2); + expect(spy1.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy2.mock.calls[0][0]).toEqual({ a: 1 }); + expect(spy2.mock.calls[1][0]).toEqual({ a: 2 }); +}); + +test('creates impure mutators from pure mutators', () => { + const { mutators } = create( + {}, + { + setFoo: () => (bar: any) => ({ foo: bar }), + } + ); + + expect(typeof mutators.setFoo).toBe('function'); +}); + +test('mutators can update state', () => { + const { store, mutators } = create( + { + value: 0, + foo: 'bar', + }, + { + add: (state: any) => (increment: any) => ({ ...state, value: state.value + increment }), + setFoo: (state: any) => (bar: any) => ({ ...state, foo: bar }), + } + ); + + expect(store.get()).toEqual({ + value: 0, + foo: 'bar', + }); + + mutators.add(11); + mutators.setFoo('baz'); + + expect(store.get()).toEqual({ + value: 11, + foo: 'baz', + }); + + mutators.add(-20); + mutators.setFoo('bazooka'); + + expect(store.get()).toEqual({ + value: -9, + foo: 'bazooka', + }); +}); + +test('mutators methods are not bound', () => { + const { store, mutators } = create( + { value: -3 }, + { + add: (state: { value: number }) => (increment: number) => ({ + ...state, + value: state.value + increment, + }), + } + ); + + expect(store.get()).toEqual({ value: -3 }); + mutators.add(4); + expect(store.get()).toEqual({ value: 1 }); +}); + +test('created mutators are saved in store object', () => { + const { store, mutators } = create( + { value: -3 }, + { + add: (state: { value: number }) => (increment: number) => ({ + ...state, + value: state.value + increment, + }), + } + ); + + expect(typeof store.transitions.add).toBe('function'); + mutators.add(5); + expect(store.get()).toEqual({ value: 2 }); +}); + +test('throws when state is modified inline - 1', () => { + const container = createStateContainer({ a: 'b' }, {}); + + let error: TypeError | null = null; + try { + (container.get().a as any) = 'c'; + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(TypeError); +}); + +test('throws when state is modified inline - 2', () => { + const container = createStateContainer({ a: 'b' }, {}); + + let error: TypeError | null = null; + try { + (container.getState().a as any) = 'c'; + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(TypeError); +}); + +test('throws when state is modified inline in subscription', done => { + const container = createStateContainer({ a: 'b' }, { set: () => (newState: any) => newState }); + + container.subscribe(value => { + let error: TypeError | null = null; + try { + (value.a as any) = 'd'; + } catch (err) { + error = err; + } + expect(error).toBeInstanceOf(TypeError); + done(); + }); + container.transitions.set({ a: 'c' }); +}); + +describe('selectors', () => { + test('can specify no selectors, or can skip them', () => { + createStateContainer({}, {}); + createStateContainer({}, {}, {}); + }); + + test('selector object is available on .selectors key', () => { + const container1 = createStateContainer({}, {}, {}); + const container2 = createStateContainer({}, {}, { foo: () => () => 123 }); + const container3 = createStateContainer({}, {}, { bar: () => () => 1, baz: () => () => 1 }); + + expect(Object.keys(container1.selectors).sort()).toEqual([]); + expect(Object.keys(container2.selectors).sort()).toEqual(['foo']); + expect(Object.keys(container3.selectors).sort()).toEqual(['bar', 'baz']); + }); + + test('selector without arguments returns correct state slice', () => { + const container = createStateContainer( + { name: 'Oleg' }, + { + changeName: (state: { name: string }) => (name: string) => ({ ...state, name }), + }, + { getName: (state: { name: string }) => () => state.name } + ); + + expect(container.selectors.getName()).toBe('Oleg'); + container.transitions.changeName('Britney'); + expect(container.selectors.getName()).toBe('Britney'); + }); + + test('selector can accept an argument', () => { + const container = createStateContainer( + { + users: { + 1: { + name: 'Darth', + }, + }, + }, + {}, + { + getUser: (state: any) => (id: number) => state.users[id], + } + ); + + expect(container.selectors.getUser(1)).toEqual({ name: 'Darth' }); + expect(container.selectors.getUser(2)).toBe(undefined); + }); + + test('selector can accept multiple arguments', () => { + const container = createStateContainer( + { + users: { + 5: { + name: 'Darth', + surname: 'Vader', + }, + }, + }, + {}, + { + getName: (state: any) => (id: number, which: 'name' | 'surname') => state.users[id][which], + } + ); + + expect(container.selectors.getName(5, 'name')).toEqual('Darth'); + expect(container.selectors.getName(5, 'surname')).toEqual('Vader'); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.ts new file mode 100644 index 0000000000000..1ef4a1c012817 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.ts @@ -0,0 +1,89 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; +import { skip } from 'rxjs/operators'; +import { RecursiveReadonly } from '@kbn/utility-types'; +import { + PureTransitionsToTransitions, + PureTransition, + ReduxLikeStateContainer, + PureSelectorsToSelectors, +} from './types'; + +const $$observable = (typeof Symbol === 'function' && (Symbol as any).observable) || '@@observable'; + +const freeze: (value: T) => RecursiveReadonly = + process.env.NODE_ENV !== 'production' + ? (value: T): RecursiveReadonly => { + if (!value) return value as RecursiveReadonly; + if (value instanceof Array) return value as RecursiveReadonly; + if (typeof value === 'object') return Object.freeze({ ...value }) as RecursiveReadonly; + else return value as RecursiveReadonly; + } + : (value: T) => value as RecursiveReadonly; + +export const createStateContainer = < + State, + PureTransitions extends object, + PureSelectors extends object = {} +>( + defaultState: State, + pureTransitions: PureTransitions, + pureSelectors: PureSelectors = {} as PureSelectors +): ReduxLikeStateContainer => { + const data$ = new BehaviorSubject>(freeze(defaultState)); + const state$ = data$.pipe(skip(1)); + const get = () => data$.getValue(); + const container: ReduxLikeStateContainer = { + get, + state$, + getState: () => data$.getValue(), + set: (state: State) => { + data$.next(freeze(state)); + }, + reducer: (state, action) => { + const pureTransition = (pureTransitions as Record>)[ + action.type + ]; + return pureTransition ? freeze(pureTransition(state)(...action.args)) : state; + }, + replaceReducer: nextReducer => (container.reducer = nextReducer), + dispatch: action => data$.next(container.reducer(get(), action)), + transitions: Object.keys(pureTransitions).reduce>( + (acc, type) => ({ ...acc, [type]: (...args: any) => container.dispatch({ type, args }) }), + {} as PureTransitionsToTransitions + ), + selectors: Object.keys(pureSelectors).reduce>( + (acc, selector) => ({ + ...acc, + [selector]: (...args: any) => (pureSelectors as any)[selector](get())(...args), + }), + {} as PureSelectorsToSelectors + ), + addMiddleware: middleware => + (container.dispatch = middleware(container as any)(container.dispatch)), + subscribe: (listener: (state: RecursiveReadonly) => void) => { + const subscription = state$.subscribe(listener); + return () => subscription.unsubscribe(); + }, + [$$observable]: state$, + }; + return container; +}; diff --git a/src/plugins/kibana_utils/public/store/react.test.tsx b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx similarity index 65% rename from src/plugins/kibana_utils/public/store/react.test.tsx rename to src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx index e629e9d0e1257..8f5810f3e147d 100644 --- a/src/plugins/kibana_utils/public/store/react.test.tsx +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx @@ -20,8 +20,17 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { act, Simulate } from 'react-dom/test-utils'; -import { createStore } from './create_store'; -import { createContext } from './react'; +import { createStateContainer } from './create_state_container'; +import { createStateContainerReactHelpers } from './create_state_container_react_helpers'; + +const create = (state: S, transitions: T = {} as T) => { + const pureTransitions = { + set: () => (newState: S) => newState, + ...transitions, + }; + const store = createStateContainer(state, pureTransitions); + return { store, mutators: store.transitions }; +}; let container: HTMLDivElement | null; @@ -36,27 +45,23 @@ afterEach(() => { }); test('can create React context', () => { - const store = createStore({ foo: 'bar' }); - const context = createContext(store); + const context = createStateContainerReactHelpers(); expect(context).toMatchObject({ - Provider: expect.any(Function), - Consumer: expect.any(Function), + Provider: expect.any(Object), + Consumer: expect.any(Object), connect: expect.any(Function), - context: { - Provider: expect.any(Object), - Consumer: expect.any(Object), - }, + context: expect.any(Object), }); }); test(' passes state to ', () => { - const store = createStore({ hello: 'world' }); - const { Provider, Consumer } = createContext(store); + const { store } = create({ hello: 'world' }); + const { Provider, Consumer } = createStateContainerReactHelpers(); ReactDOM.render( - - {({ hello }) => hello} + + {(s: typeof store) => s.get().hello} , container ); @@ -74,8 +79,8 @@ interface Props1 { } test(' passes state to connect()()', () => { - const store = createStore({ hello: 'Bob' }); - const { Provider, connect } = createContext(store); + const { store } = create({ hello: 'Bob' }); + const { Provider, connect } = createStateContainerReactHelpers(); const Demo: React.FC = ({ message, stop }) => ( <> @@ -87,7 +92,7 @@ test(' passes state to connect()()', () => { const DemoConnected = connect(mergeProps)(Demo); ReactDOM.render( - + , container @@ -97,13 +102,13 @@ test(' passes state to connect()()', () => { }); test('context receives Redux store', () => { - const store = createStore({ foo: 'bar' }); - const { Provider, context } = createContext(store); + const { store } = create({ foo: 'bar' }); + const { Provider, context } = createStateContainerReactHelpers(); ReactDOM.render( /* eslint-disable no-shadow */ - - {({ store }) => store.getState().foo} + + {store => store.get().foo} , /* eslint-enable no-shadow */ container @@ -117,16 +122,16 @@ xtest('can use multiple stores in one React app', () => {}); describe('hooks', () => { describe('useStore', () => { test('can select store using useStore hook', () => { - const store = createStore({ foo: 'bar' }); - const { Provider, useStore } = createContext(store); + const { store } = create({ foo: 'bar' }); + const { Provider, useContainer } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { // eslint-disable-next-line no-shadow - const store = useStore(); + const store = useContainer(); return <>{store.get().foo}; }; ReactDOM.render( - + , container @@ -138,15 +143,15 @@ describe('hooks', () => { describe('useState', () => { test('can select state using useState hook', () => { - const store = createStore({ foo: 'qux' }); - const { Provider, useState } = createContext(store); + const { store } = create({ foo: 'qux' }); + const { Provider, useState } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const { foo } = useState(); return <>{foo}; }; ReactDOM.render( - + , container @@ -156,18 +161,23 @@ describe('hooks', () => { }); test('re-renders when state changes', () => { - const store = createStore({ foo: 'bar' }); - const { setFoo } = store.createMutators({ - setFoo: state => foo => ({ ...state, foo }), - }); - const { Provider, useState } = createContext(store); + const { + store, + mutators: { setFoo }, + } = create( + { foo: 'bar' }, + { + setFoo: (state: { foo: string }) => (foo: string) => ({ ...state, foo }), + } + ); + const { Provider, useState } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const { foo } = useState(); return <>{foo}; }; ReactDOM.render( - + , container @@ -181,26 +191,31 @@ describe('hooks', () => { }); }); - describe('useMutations', () => { - test('useMutations hook returns mutations that can update state', () => { - const store = createStore< + describe('useTransitions', () => { + test('useTransitions hook returns mutations that can update state', () => { + const { store } = create< { cnt: number; }, + any + >( { - increment: (value: number) => void; + cnt: 0, + }, + { + increment: (state: { cnt: number }) => (value: number) => ({ + ...state, + cnt: state.cnt + value, + }), } - >({ - cnt: 0, - }); - store.createMutators({ - increment: state => value => ({ ...state, cnt: state.cnt + value }), - }); + ); - const { Provider, useState, useMutators } = createContext(store); + const { Provider, useState, useTransitions } = createStateContainerReactHelpers< + typeof store + >(); const Demo: React.FC<{}> = () => { const { cnt } = useState(); - const { increment } = useMutators(); + const { increment } = useTransitions(); return ( <> {cnt} @@ -210,7 +225,7 @@ describe('hooks', () => { }; ReactDOM.render( - + , container @@ -230,7 +245,7 @@ describe('hooks', () => { describe('useSelector', () => { test('can select deeply nested value', () => { - const store = createStore({ + const { store } = create({ foo: { bar: { baz: 'qux', @@ -238,14 +253,14 @@ describe('hooks', () => { }, }); const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; - const { Provider, useSelector } = createContext(store); + const { Provider, useSelector } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const value = useSelector(selector); return <>{value}; }; ReactDOM.render( - + , container @@ -255,7 +270,7 @@ describe('hooks', () => { }); test('re-renders when state changes', () => { - const store = createStore({ + const { store, mutators } = create({ foo: { bar: { baz: 'qux', @@ -263,14 +278,14 @@ describe('hooks', () => { }, }); const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; - const { Provider, useSelector } = createContext(store); + const { Provider, useSelector } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const value = useSelector(selector); return <>{value}; }; ReactDOM.render( - + , container @@ -278,7 +293,7 @@ describe('hooks', () => { expect(container!.innerHTML).toBe('qux'); act(() => { - store.set({ + mutators.set({ foo: { bar: { baz: 'quux', @@ -290,9 +305,9 @@ describe('hooks', () => { }); test("re-renders only when selector's result changes", async () => { - const store = createStore({ a: 'b', foo: 'bar' }); + const { store, mutators } = create({ a: 'b', foo: 'bar' }); const selector = (state: { foo: string }) => state.foo; - const { Provider, useSelector } = createContext(store); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -301,7 +316,7 @@ describe('hooks', () => { return <>{value}; }; ReactDOM.render( - + , container @@ -311,24 +326,24 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - store.set({ a: 'c', foo: 'bar' }); + mutators.set({ a: 'c', foo: 'bar' }); }); await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(1); act(() => { - store.set({ a: 'd', foo: 'bar 2' }); + mutators.set({ a: 'd', foo: 'bar 2' }); }); await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(2); }); - test('re-renders on same shape object', async () => { - const store = createStore({ foo: { bar: 'baz' } }); + test('does not re-render on same shape object', async () => { + const { store, mutators } = create({ foo: { bar: 'baz' } }); const selector = (state: { foo: any }) => state.foo; - const { Provider, useSelector } = createContext(store); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -337,7 +352,7 @@ describe('hooks', () => { return <>{JSON.stringify(value)}; }; ReactDOM.render( - + , container @@ -347,7 +362,14 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - store.set({ foo: { bar: 'baz' } }); + mutators.set({ foo: { bar: 'baz' } }); + }); + + await new Promise(r => setTimeout(r, 1)); + expect(cnt).toBe(1); + + act(() => { + mutators.set({ foo: { bar: 'qux' } }); }); await new Promise(r => setTimeout(r, 1)); @@ -355,10 +377,15 @@ describe('hooks', () => { }); test('can set custom comparator function to prevent re-renders on deep equality', async () => { - const store = createStore({ foo: { bar: 'baz' } }); + const { store, mutators } = create( + { foo: { bar: 'baz' } }, + { + set: () => (newState: { foo: { bar: string } }) => newState, + } + ); const selector = (state: { foo: any }) => state.foo; const comparator = (prev: any, curr: any) => JSON.stringify(prev) === JSON.stringify(curr); - const { Provider, useSelector } = createContext(store); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -367,7 +394,7 @@ describe('hooks', () => { return <>{JSON.stringify(value)}; }; ReactDOM.render( - + , container @@ -377,7 +404,7 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - store.set({ foo: { bar: 'baz' } }); + mutators.set({ foo: { bar: 'baz' } }); }); await new Promise(r => setTimeout(r, 1)); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts new file mode 100644 index 0000000000000..e94165cc48376 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts @@ -0,0 +1,77 @@ +/* + * 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 * as React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import defaultComparator from 'fast-deep-equal'; +import { Comparator, Connect, StateContainer, UnboxState } from './types'; + +const { useContext, useLayoutEffect, useRef, createElement: h } = React; + +export const createStateContainerReactHelpers = >() => { + const context = React.createContext(null as any); + + const useContainer = (): Container => useContext(context); + + const useState = (): UnboxState => { + const { state$, get } = useContainer(); + const value = useObservable(state$, get()); + return value; + }; + + const useTransitions = () => useContainer().transitions; + + const useSelector = ( + selector: (state: UnboxState) => Result, + comparator: Comparator = defaultComparator + ): Result => { + const { state$, get } = useContainer(); + const lastValueRef = useRef(get()); + const [value, setValue] = React.useState(() => { + const newValue = selector(get()); + lastValueRef.current = newValue; + return newValue; + }); + useLayoutEffect(() => { + const subscription = state$.subscribe((currentState: UnboxState) => { + const newValue = selector(currentState); + if (!comparator(lastValueRef.current, newValue)) { + lastValueRef.current = newValue; + setValue(newValue); + } + }); + return () => subscription.unsubscribe(); + }, [state$, comparator]); + return value; + }; + + const connect: Connect> = mapStateToProp => component => props => + h(component, { ...useSelector(mapStateToProp), ...props } as any); + + return { + Provider: context.Provider, + Consumer: context.Consumer, + context, + useContainer, + useState, + useTransitions, + useSelector, + connect, + }; +}; diff --git a/src/plugins/kibana_utils/public/store/index.ts b/src/plugins/kibana_utils/public/state_containers/index.ts similarity index 86% rename from src/plugins/kibana_utils/public/store/index.ts rename to src/plugins/kibana_utils/public/state_containers/index.ts index 468e8ab8c5ade..43e204ecb79f7 100644 --- a/src/plugins/kibana_utils/public/store/index.ts +++ b/src/plugins/kibana_utils/public/state_containers/index.ts @@ -17,5 +17,6 @@ * under the License. */ -export * from './create_store'; -export * from './react'; +export * from './types'; +export * from './create_state_container'; +export * from './create_state_container_react_helpers'; diff --git a/src/plugins/kibana_utils/public/state_containers/types.ts b/src/plugins/kibana_utils/public/state_containers/types.ts new file mode 100644 index 0000000000000..e0a1a18972635 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/types.ts @@ -0,0 +1,99 @@ +/* + * 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 { Observable } from 'rxjs'; +import { Ensure, RecursiveReadonly } from '@kbn/utility-types'; + +export interface TransitionDescription { + type: Type; + args: Args; +} +export type Transition = (...args: Args) => State; +export type PureTransition = ( + state: RecursiveReadonly +) => Transition; +export type EnsurePureTransition = Ensure>; +export type PureTransitionToTransition> = ReturnType; +export type PureTransitionsToTransitions = { + [K in keyof T]: PureTransitionToTransition>; +}; + +export interface BaseStateContainer { + get: () => RecursiveReadonly; + set: (state: State) => void; + state$: Observable>; +} + +export interface StateContainer< + State, + PureTransitions extends object, + PureSelectors extends object = {} +> extends BaseStateContainer { + transitions: Readonly>; + selectors: Readonly>; +} + +export interface ReduxLikeStateContainer< + State, + PureTransitions extends object, + PureSelectors extends object = {} +> extends StateContainer { + getState: () => RecursiveReadonly; + reducer: Reducer>; + replaceReducer: (nextReducer: Reducer>) => void; + dispatch: (action: TransitionDescription) => void; + addMiddleware: (middleware: Middleware>) => void; + subscribe: (listener: (state: RecursiveReadonly) => void) => () => void; +} + +export type Dispatch = (action: T) => void; + +export type Middleware = ( + store: Pick, 'getState' | 'dispatch'> +) => ( + next: (action: TransitionDescription) => TransitionDescription | any +) => Dispatch; + +export type Reducer = (state: State, action: TransitionDescription) => State; + +export type UnboxState< + Container extends StateContainer +> = Container extends StateContainer ? T : never; +export type UnboxTransitions< + Container extends StateContainer +> = Container extends StateContainer ? T : never; + +export type Selector = (...args: Args) => Result; +export type PureSelector = ( + state: State +) => Selector; +export type EnsurePureSelector = Ensure>; +export type PureSelectorToSelector> = ReturnType< + EnsurePureSelector +>; +export type PureSelectorsToSelectors = { + [K in keyof T]: PureSelectorToSelector>; +}; + +export type Comparator = (previous: Result, current: Result) => boolean; + +export type MapStateToProps = (state: State) => StateProps; +export type Connect = ( + mapStateToProp: MapStateToProps> +) => (component: React.ComponentType) => React.FC>; diff --git a/src/plugins/kibana_utils/public/store/create_store.test.ts b/src/plugins/kibana_utils/public/store/create_store.test.ts deleted file mode 100644 index cfdeb76254003..0000000000000 --- a/src/plugins/kibana_utils/public/store/create_store.test.ts +++ /dev/null @@ -1,177 +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 { createStore } from './create_store'; - -test('can create store', () => { - const store = createStore({}); - expect(store).toMatchObject({ - get: expect.any(Function), - set: expect.any(Function), - state$: expect.any(Object), - createMutators: expect.any(Function), - mutators: expect.any(Object), - redux: { - getState: expect.any(Function), - dispatch: expect.any(Function), - subscribe: expect.any(Function), - }, - }); -}); - -test('can set default state', () => { - const defaultState = { - foo: 'bar', - }; - const store = createStore(defaultState); - expect(store.get()).toEqual(defaultState); - expect(store.redux.getState()).toEqual(defaultState); -}); - -test('can set state', () => { - const defaultState = { - foo: 'bar', - }; - const newState = { - foo: 'baz', - }; - const store = createStore(defaultState); - - store.set(newState); - - expect(store.get()).toEqual(newState); - expect(store.redux.getState()).toEqual(newState); -}); - -test('does not shallow merge states', () => { - const defaultState = { - foo: 'bar', - }; - const newState = { - foo2: 'baz', - }; - const store = createStore(defaultState); - - store.set(newState); - - expect(store.get()).toEqual(newState); - expect(store.redux.getState()).toEqual(newState); -}); - -test('can subscribe and unsubscribe to state changes', () => { - const store = createStore({}); - const spy = jest.fn(); - const subscription = store.state$.subscribe(spy); - store.set({ a: 1 }); - store.set({ a: 2 }); - subscription.unsubscribe(); - store.set({ a: 3 }); - - expect(spy).toHaveBeenCalledTimes(2); - expect(spy.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy.mock.calls[1][0]).toEqual({ a: 2 }); -}); - -test('multiple subscribers can subscribe', () => { - const store = createStore({}); - const spy1 = jest.fn(); - const spy2 = jest.fn(); - const subscription1 = store.state$.subscribe(spy1); - const subscription2 = store.state$.subscribe(spy2); - store.set({ a: 1 }); - subscription1.unsubscribe(); - store.set({ a: 2 }); - subscription2.unsubscribe(); - store.set({ a: 3 }); - - expect(spy1).toHaveBeenCalledTimes(1); - expect(spy2).toHaveBeenCalledTimes(2); - expect(spy1.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy2.mock.calls[0][0]).toEqual({ a: 1 }); - expect(spy2.mock.calls[1][0]).toEqual({ a: 2 }); -}); - -test('creates impure mutators from pure mutators', () => { - const store = createStore({}); - const mutators = store.createMutators({ - setFoo: _ => bar => ({ foo: bar }), - }); - - expect(typeof mutators.setFoo).toBe('function'); -}); - -test('mutators can update state', () => { - const store = createStore({ - value: 0, - foo: 'bar', - }); - const mutators = store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - setFoo: state => bar => ({ ...state, foo: bar }), - }); - - expect(store.get()).toEqual({ - value: 0, - foo: 'bar', - }); - - mutators.add(11); - mutators.setFoo('baz'); - - expect(store.get()).toEqual({ - value: 11, - foo: 'baz', - }); - - mutators.add(-20); - mutators.setFoo('bazooka'); - - expect(store.get()).toEqual({ - value: -9, - foo: 'bazooka', - }); -}); - -test('mutators methods are not bound', () => { - const store = createStore({ value: -3 }); - const { add } = store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - }); - - expect(store.get()).toEqual({ value: -3 }); - add(4); - expect(store.get()).toEqual({ value: 1 }); -}); - -test('created mutators are saved in store object', () => { - const store = createStore< - any, - { - add: (increment: number) => void; - } - >({ value: -3 }); - - store.createMutators({ - add: state => increment => ({ ...state, value: state.value + increment }), - }); - - expect(typeof store.mutators.add).toBe('function'); - store.mutators.add(5); - expect(store.get()).toEqual({ value: 2 }); -}); diff --git a/src/plugins/kibana_utils/public/store/create_store.ts b/src/plugins/kibana_utils/public/store/create_store.ts deleted file mode 100644 index 315523360f92d..0000000000000 --- a/src/plugins/kibana_utils/public/store/create_store.ts +++ /dev/null @@ -1,85 +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 { createStore as createReduxStore, Reducer } from 'redux'; -import { Subject, Observable } from 'rxjs'; -import { AppStore, Mutators, PureMutators } from './types'; - -const SET = '__SET__'; - -export const createStore = < - State extends {}, - StateMutators extends Mutators> = {} ->( - defaultState: State -): AppStore => { - const pureMutators: PureMutators = {}; - const mutators: StateMutators = {} as StateMutators; - const reducer: Reducer = (state, action) => { - const pureMutator = pureMutators[action.type]; - if (pureMutator) { - return pureMutator(state)(...action.args); - } - - switch (action.type) { - case SET: - return action.state; - default: - return state; - } - }; - const redux = createReduxStore(reducer, defaultState as any); - - const get = redux.getState; - - const set = (state: State) => - redux.dispatch({ - type: SET, - state, - }); - - const state$ = new Subject(); - redux.subscribe(() => { - state$.next(get()); - }); - - const createMutators: AppStore['createMutators'] = newPureMutators => { - const result: Mutators = {}; - for (const type of Object.keys(newPureMutators)) { - result[type] = (...args) => { - redux.dispatch({ - type, - args, - }); - }; - } - Object.assign(pureMutators, newPureMutators); - Object.assign(mutators, result); - return result; - }; - - return { - get, - set, - redux, - state$: (state$ as unknown) as Observable, - createMutators, - mutators, - }; -}; diff --git a/src/plugins/kibana_utils/public/store/observable_selector.ts b/src/plugins/kibana_utils/public/store/observable_selector.ts deleted file mode 100644 index 6ba6f42296a6c..0000000000000 --- a/src/plugins/kibana_utils/public/store/observable_selector.ts +++ /dev/null @@ -1,47 +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 { Observable, BehaviorSubject } from 'rxjs'; - -export type Selector = (state: State) => Result; -export type Comparator = (previous: Result, current: Result) => boolean; -export type Unsubscribe = () => void; - -const defaultComparator: Comparator = (previous, current) => previous === current; - -export const observableSelector = ( - state: State, - state$: Observable, - selector: Selector, - comparator: Comparator = defaultComparator -): [Observable, Unsubscribe] => { - let previousResult: Result = selector(state); - const result$ = new BehaviorSubject(previousResult); - - const subscription = state$.subscribe(value => { - const result = selector(value); - const isEqual: boolean = comparator(previousResult, result); - if (!isEqual) { - result$.next(result); - } - previousResult = result; - }); - - return [(result$ as unknown) as Observable, subscription.unsubscribe]; -}; diff --git a/src/plugins/kibana_utils/public/store/react.ts b/src/plugins/kibana_utils/public/store/react.ts deleted file mode 100644 index 00861b2b0b8fe..0000000000000 --- a/src/plugins/kibana_utils/public/store/react.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import { Provider as ReactReduxProvider, connect as reactReduxConnect } from 'react-redux'; -import { Store } from 'redux'; -import { AppStore, Mutators, PureMutators } from './types'; -import { observableSelector, Selector, Comparator } from './observable_selector'; -// TODO: Below import is temporary, use `react-use` lib instead. -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { useObservable } from '../../../kibana_react/public/util/use_observable'; - -const { useMemo, useLayoutEffect, useContext, createElement, Fragment } = React; - -/** - * @note - * Types in `react-redux` seem to be quite off compared to reality - * that's why a lot of `any`s below. - */ - -export interface ConsumerProps { - children: (state: State) => React.ReactChild; -} - -export type MapStateToProps = (state: State) => StateProps; - -// TODO: `Omit` is generally part of TypeScript, but it currently does not exist in our build. -type Omit = Pick>; -export type Connect = ( - mapStateToProp: MapStateToProps> -) => (component: React.ComponentType) => React.FC>; - -interface ReduxContextValue { - store: Store; -} - -const mapDispatchToProps = () => ({}); -const mergeProps: any = (stateProps: any, dispatchProps: any, ownProps: any) => ({ - ...ownProps, - ...stateProps, - ...dispatchProps, -}); - -export const createContext = < - State extends {}, - StateMutators extends Mutators> = {} ->( - store: AppStore -) => { - const { redux } = store; - (redux as any).__appStore = store; - const context = React.createContext({ store: redux }); - - const useStore = (): AppStore => { - // eslint-disable-next-line no-shadow - const { store } = useContext(context); - return (store as any).__appStore; - }; - - const useState = (): State => { - const { state$, get } = useStore(); - const state = useObservable(state$, get()); - return state; - }; - - const useMutators = (): StateMutators => useStore().mutators; - - const useSelector = ( - selector: Selector, - comparator?: Comparator - ): Result => { - const { state$, get } = useStore(); - /* eslint-disable react-hooks/exhaustive-deps */ - const [observable$, unsubscribe] = useMemo( - () => observableSelector(get(), state$, selector, comparator), - [state$] - ); - /* eslint-enable react-hooks/exhaustive-deps */ - useLayoutEffect(() => unsubscribe, [observable$, unsubscribe]); - const value = useObservable(observable$, selector(get())); - return value; - }; - - const Provider: React.FC<{}> = ({ children }) => - createElement(ReactReduxProvider, { - store: redux, - context, - children, - } as any); - - const Consumer: React.FC> = ({ children }) => { - const state = useState(); - return createElement(Fragment, { children: children(state) }); - }; - - const options: any = { context }; - const connect: Connect = mapStateToProps => - reactReduxConnect(mapStateToProps, mapDispatchToProps, mergeProps, options) as any; - - return { - Provider, - Consumer, - connect, - context, - useStore, - useState, - useMutators, - useSelector, - }; -}; diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index b76da4b4eca3e..f4659ffa120d4 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -21,20 +21,26 @@ import sinon from 'sinon'; // TODO: We should not be importing from the data plugin directly here; this is only necessary // because it is one of the few places that we need to access the IndexPattern class itself, rather // than just the type. Doing this as a temporary measure; it will be left behind when migrating to NP. -import { IndexPattern } from '../../legacy/core_plugins/data/public/'; + import { FieldList, - getRoutes, - formatHitProvider, - flattenHitWrapper, -} from 'ui/index_patterns'; -import { FIELD_FORMAT_IDS, + IndexPattern, + indexPatterns, } from '../../plugins/data/public'; +import { setFieldFormats } from '../../plugins/data/public/services'; + +setFieldFormats({ + getDefaultInstance: () => ({ + getConverterFor: () => value => value, + convert: value => JSON.stringify(value) + }), +}); + import { getFieldFormatsRegistry } from './stub_field_formats'; -export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { +export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { const registeredFieldFormats = getFieldFormatsRegistry(uiSettings); this.id = pattern; @@ -49,11 +55,11 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields, this.getSourceFiltering = sinon.stub(); this.metaFields = ['_id', '_type', '_source']; this.fieldFormatMap = {}; - this.routes = getRoutes(); + this.routes = indexPatterns.getRoutes(); this.getComputedFields = IndexPattern.prototype.getComputedFields.bind(this); - this.flattenHit = flattenHitWrapper(this, this.metaFields); - this.formatHit = formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); + this.flattenHit = indexPatterns.flattenHitWrapper(this, this.metaFields); + this.formatHit = indexPatterns.formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); this.fieldsFetcher = { apiClient: { baseUrl: '' } }; this.formatField = this.formatHit.formatField; diff --git a/test/functional/apps/discover/_saved_queries.js b/test/functional/apps/discover/_saved_queries.js index 8fbc40f86e8dc..3ae8f51fb76dc 100644 --- a/test/functional/apps/discover/_saved_queries.js +++ b/test/functional/apps/discover/_saved_queries.js @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + const browser = getService('browser'); const defaultSettings = { defaultIndex: 'logstash-*', @@ -86,6 +87,17 @@ export default function ({ getService, getPageObjects }) { expect(timePickerValues.end).to.not.eql(PageObjects.timePicker.defaultEndTime); }); + it('preserves the currently loaded query when the page is reloaded', async () => { + await browser.refresh(); + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); + expect(timePickerValues.start).to.not.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.not.eql(PageObjects.timePicker.defaultEndTime); + expect(await PageObjects.discover.getHitCount()).to.be('2,792'); + expect(await savedQueryManagementComponent.getCurrentlyLoadedQueryID()).to.be('OkResponse'); + }); + + it('allows saving changes to a currently loaded query via the saved query management component', async () => { await queryBar.setQuery('response:404'); await savedQueryManagementComponent.updateCurrentlyLoadedQuery( diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 937f703308881..ed45b3fbe069b 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -288,7 +288,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } async getSharedItemContainers() { - const cssSelector = '[data-shared-item-container]'; + const cssSelector = '[data-shared-items-container]'; return find.allByCssSelector(cssSelector); } diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts index d6de0be0c172e..9f0a8ded649b2 100644 --- a/test/functional/services/saved_query_management_component.ts +++ b/test/functional/services/saved_query_management_component.ts @@ -26,6 +26,15 @@ export function SavedQueryManagementComponentProvider({ getService }: FtrProvide const retry = getService('retry'); class SavedQueryManagementComponent { + public async getCurrentlyLoadedQueryID() { + await this.openSavedQueryManagementComponent(); + try { + return await testSubjects.getVisibleText('~saved-query-list-item-selected'); + } catch { + return undefined; + } + } + public async saveNewQuery( name: string, description: string, diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 5b3cd071316e6..77907a07addd1 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -262,10 +262,13 @@ def buildXpack() { } def runErrorReporter() { + def status = buildUtils.getBuildStatus() + def dryRun = status != "ABORTED" ? "" : "--no-github-update" + bash( """ source src/dev/ci_setup/setup_env.sh - node scripts/report_failed_tests + node scripts/report_failed_tests ${dryRun} """, "Report failed tests, if necessary" ) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 6d0da2f0b693d..180aafe504c63 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -9,6 +9,7 @@ "xpack.canvas": "legacy/plugins/canvas", "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", + "xpack.endpoint": "plugins/endpoint", "xpack.features": "plugins/features", "xpack.fileUpload": "legacy/plugins/file_upload", "xpack.graph": "legacy/plugins/graph", @@ -18,20 +19,20 @@ "xpack.infra": "legacy/plugins/infra", "xpack.kueryAutocomplete": "legacy/plugins/kuery_autocomplete", "xpack.lens": "legacy/plugins/lens", - "xpack.licensing": "plugins/licensing", "xpack.licenseMgmt": "legacy/plugins/license_management", - "xpack.maps": "legacy/plugins/maps", - "xpack.ml": "legacy/plugins/ml", + "xpack.licensing": "plugins/licensing", "xpack.logstash": "legacy/plugins/logstash", "xpack.main": "legacy/plugins/xpack_main", + "xpack.maps": "legacy/plugins/maps", + "xpack.ml": "legacy/plugins/ml", "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "legacy/plugins/remote_clusters", "xpack.reporting": [ "plugins/reporting", "legacy/plugins/reporting" ], "xpack.rollupJobs": "legacy/plugins/rollup", "xpack.searchProfiler": "legacy/plugins/searchprofiler", - "xpack.siem": "legacy/plugins/siem", "xpack.security": ["legacy/plugins/security", "plugins/security"], "xpack.server": "legacy/server", + "xpack.siem": "legacy/plugins/siem", "xpack.snapshotRestore": "legacy/plugins/snapshot_restore", "xpack.spaces": ["legacy/plugins/spaces", "plugins/spaces"], "xpack.taskManager": "legacy/plugins/task_manager", diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx new file mode 100644 index 0000000000000..21a39e19657a1 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx @@ -0,0 +1,17 @@ +/* + * 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 { shallow } from 'enzyme'; +import { ServiceNodeMetrics } from '.'; + +describe('ServiceNodeMetrics', () => { + describe('render', () => { + it('renders', () => { + expect(() => shallow()).not.toThrowError(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx index 79874d6648d0f..3929c153ae419 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx @@ -117,6 +117,22 @@ export function ServiceNodeMetrics() { ) : ( + + + {serviceName} + + } + /> + ; }; }; + savedObjects: { + client: { + get: jest.Mock; + }; + }; }; }; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts index a09cdbf91ec6e..56c9255844009 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts @@ -73,7 +73,10 @@ export async function setupRequest( const { config } = context; const { query } = context.params; - const indices = await getApmIndices(context); + const indices = await getApmIndices({ + savedObjectsClient: context.core.savedObjects.client, + config + }); const dynamicIndexPattern = await getDynamicIndexPattern({ context, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index 0ed30ec4cdd27..e451f89af5620 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -54,15 +54,25 @@ export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig { }; } -export async function getApmIndices(context: APMRequestHandlerContext) { +// export async function getApmIndices(context: APMRequestHandlerContext) { +// return _getApmIndices(context.core, context.config); +// } + +export async function getApmIndices({ + config, + savedObjectsClient +}: { + config: APMConfig; + savedObjectsClient: SavedObjectsClientContract; +}) { try { const apmIndicesSavedObject = await getApmIndicesSavedObject( - context.core.savedObjects.client + savedObjectsClient ); - const apmIndicesConfig = getApmIndicesConfig(context.config); + const apmIndicesConfig = getApmIndicesConfig(config); return merge({}, apmIndicesConfig, apmIndicesSavedObject); } catch (error) { - return getApmIndicesConfig(context.config); + return getApmIndicesConfig(config); } } diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts index b66eb05f6eda5..a69fba52be3f0 100644 --- a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts @@ -26,7 +26,10 @@ export const apmIndicesRoute = createRoute(() => ({ method: 'GET', path: '/api/apm/settings/apm-indices', handler: async ({ context }) => { - return await getApmIndices(context); + return await getApmIndices({ + savedObjectsClient: context.core.savedObjects.client, + config: context.config + }); } })); 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 83c610800b89b..8771181639f4d 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 @@ -8,7 +8,6 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; -import { setup as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; const getAutocompleteProvider = (language: string) => npStart.plugins.data.autocomplete.getProvider(language); @@ -64,7 +63,7 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { if (this.cachedIndexPattern) { return this.cachedIndexPattern; } - const res = await data.indexPatterns.indexPatterns.getFieldsForWildcard({ + const res = await npStart.plugins.data.indexPatterns.getFieldsForWildcard({ pattern: this.indexPatternName, }); if (isEmpty(res.fields)) { diff --git a/x-pack/legacy/plugins/file_upload/public/kibana_services.js b/x-pack/legacy/plugins/file_upload/public/kibana_services.js index 3c00ab5709660..1645040629195 100644 --- a/x-pack/legacy/plugins/file_upload/public/kibana_services.js +++ b/x-pack/legacy/plugins/file_upload/public/kibana_services.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { DEFAULT_KBN_VERSION } from '../common/constants/file_import'; -export const indexPatternService = data.indexPatterns.indexPatterns; +export const indexPatternService = npStart.plugins.data.indexPatterns; export let savedObjectsClient; export let basePath; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 8d5e27f1e9aec..9dec725a7a2a1 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -51,6 +51,9 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { query: { savedQueries: {}, }, + autocomplete: { + getProvider: () => undefined, + }, }, }; @@ -63,8 +66,7 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { ); } -// FLAKY: https://github.com/elastic/kibana/issues/52246 -describe.skip('search_bar', () => { +describe('search_bar', () => { const defaultProps = { isLoading: false, onQuerySubmit: jest.fn(), @@ -89,21 +91,23 @@ describe.skip('search_bar', () => { ); }); - function mountSearchBar() { + async function mountSearchBar() { jest.clearAllMocks(); const wrappedSearchBar = wrapSearchBarInContext({ ...defaultProps }); - instance = mountWithIntl({wrappedSearchBar}); + await act(async () => { + instance = mountWithIntl({wrappedSearchBar}); + }); } - it('should render search bar and fetch index pattern', () => { - mountSearchBar(); + it('should render search bar and fetch index pattern', async () => { + await mountSearchBar(); expect(defaultProps.indexPatternProvider.get).toHaveBeenCalledWith('123'); }); it('should render search bar and submit queries', async () => { - mountSearchBar(); + await mountSearchBar(); await waitForIndexPatternFetch(); @@ -119,7 +123,7 @@ describe.skip('search_bar', () => { }); it('should translate kql query into JSON dsl', async () => { - mountSearchBar(); + await mountSearchBar(); await waitForIndexPatternFetch(); @@ -137,8 +141,8 @@ describe.skip('search_bar', () => { }); }); - it('should open index pattern picker', () => { - mountSearchBar(); + it('should open index pattern picker', async () => { + await mountSearchBar(); // pick the button component out of the tree because // it's part of a popover and thus not covered by enzyme diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 988aa78695095..712d08c106425 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -18,7 +18,6 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { GraphPlugin } from './plugin'; @@ -51,7 +50,6 @@ async function getAngularInjectedDependencies(): Promise; navigation: NavigationStart; } @@ -31,7 +29,6 @@ export interface GraphPluginStartDependencies { } export class GraphPlugin implements Plugin { - private dataStart: DataStart | null = null; private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; @@ -61,7 +58,7 @@ export class GraphPlugin implements Plugin { chrome: contextCore.chrome, config: contextCore.uiSettings, toastNotifications: contextCore.notifications.toasts, - indexPatterns: this.dataStart!.indexPatterns.indexPatterns, + indexPatterns: this.npDataStart!.indexPatterns, ...this.angularDependencies!, }); }, @@ -70,10 +67,9 @@ export class GraphPlugin implements Plugin { start( core: CoreStart, - { data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + { npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies ) { this.navigationStart = navigation; - this.dataStart = data; this.npDataStart = npData; this.angularDependencies = angularDependencies; this.savedObjectsClient = core.savedObjects.client; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index 1beee2e73721b..0f3c52d38a01c 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -21,7 +21,6 @@ import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_to import { confirmModalFactory } from 'ui/modals/confirm_modal'; // type imports -import { DataStart } from 'src/legacy/core_plugins/data/public'; import { AppMountContext, ChromeStart, @@ -32,7 +31,10 @@ import { } from 'kibana/public'; // @ts-ignore import { initGraphApp } from './app'; -import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; +import { + Plugin as DataPlugin, + IndexPatternsContract, +} from '../../../../../src/plugins/data/public'; import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; /** @@ -50,7 +52,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { chrome: ChromeStart; config: IUiSettingsClient; toastNotifications: ToastsStart; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; + indexPatterns: IndexPatternsContract; npData: ReturnType; savedObjectsClient: SavedObjectsClientContract; xpackInfo: { get(path: string): unknown }; diff --git a/x-pack/legacy/plugins/infra/common/http_api/metadata_api.ts b/x-pack/legacy/plugins/infra/common/http_api/metadata_api.ts index 5b9389a073002..ace61e13193c8 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/metadata_api.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/metadata_api.ts @@ -5,7 +5,6 @@ */ import * as rt from 'io-ts'; -import { InfraWrappableRequest } from '../../server/lib/adapters/framework'; export const InfraMetadataNodeTypeRT = rt.keyof({ host: null, @@ -67,6 +66,7 @@ export const InfraMetadataInfoRT = rt.partial({ }); const InfraMetadataRequiredRT = rt.type({ + id: rt.string, name: rt.string, features: rt.array(InfraMetadataFeatureRT), }); @@ -81,8 +81,6 @@ export type InfraMetadata = rt.TypeOf; export type InfraMetadataRequest = rt.TypeOf; -export type InfraMetadataWrappedRequest = InfraWrappableRequest; - export type InfraMetadataFeature = rt.TypeOf; export type InfraMetadataInfo = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts b/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts index 607d71654032e..46aab881bce4c 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts @@ -6,7 +6,6 @@ import * as rt from 'io-ts'; import { InventoryMetricRT, ItemTypeRT } from '../inventory_models/types'; -import { InfraWrappableRequest } from '../../server/lib/adapters/framework'; import { InfraTimerangeInputRT } from './snapshot_api'; const NodeDetailsDataPointRT = rt.intersection([ @@ -53,6 +52,4 @@ export const NodeDetailsRequestRT = rt.intersection([ // export type NodeDetailsRequest = InfraWrappableRequest; export type NodeDetailsRequest = rt.TypeOf; -export type NodeDetailsWrappedRequest = InfraWrappableRequest; - export type NodeDetailsMetricDataResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts index 24ca0fed73338..3e6aec4bad972 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts @@ -5,7 +5,6 @@ */ import * as rt from 'io-ts'; -import { InfraWrappableRequest } from '../../server/lib/adapters/framework'; import { SnapshotMetricTypeRT, ItemTypeRT } from '../inventory_models/types'; export const SnapshotNodePathRT = rt.intersection([ @@ -64,6 +63,5 @@ export const SnapshotRequestRT = rt.intersection([ ]); export type SnapshotRequest = rt.TypeOf; -export type SnapshotWrappedRequest = InfraWrappableRequest; export type SnapshotNode = rt.TypeOf; export type SnapshotNodeResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index 9bf679fb5ff80..dbf1f4ad61de3 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -7,9 +7,14 @@ import { i18n } from '@kbn/i18n'; import JoiNamespace from 'joi'; import { resolve } from 'path'; - -import { getConfigSchema, initServerWithKibana } from './server/kibana.index'; +import { PluginInitializerContext } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import KbnServer from 'src/legacy/server/kbn_server'; +import { getConfigSchema } from './server/kibana.index'; import { savedObjectMappings } from './server/saved_objects'; +import { plugin, InfraServerPluginDeps } from './server/new_platform_index'; +import { InfraSetup } from '../../../plugins/infra/server'; +import { APMPluginContract } from '../../../plugins/apm/server/plugin'; const APP_ID = 'infra'; const logsSampleDataLinkLabel = i18n.translate('xpack.infra.sampleDataLinkLabel', { @@ -70,9 +75,52 @@ export function infra(kibana: any) { config(Joi: typeof JoiNamespace) { return getConfigSchema(Joi); }, - init(server: any) { - initServerWithKibana(server); - server.addAppLinksToSampleDataset('logs', [ + init(legacyServer: any) { + const { newPlatform } = legacyServer as KbnServer; + const { core, plugins } = newPlatform.setup; + + const infraSetup = (plugins.infra as unknown) as InfraSetup; // chef's kiss + + const initContext = ({ + config: infraSetup.__legacy.config, + } as unknown) as PluginInitializerContext; + // NP_TODO: Use real types from the other plugins as they are migrated + const pluginDeps: InfraServerPluginDeps = { + usageCollection: plugins.usageCollection as UsageCollectionSetup, + indexPatterns: { + indexPatternsServiceFactory: legacyServer.indexPatternsServiceFactory, + }, + metrics: legacyServer.plugins.metrics, + spaces: plugins.spaces, + features: plugins.features, + // NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that + // originate from the New Platform router (and are very different to the old request object). + // Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just + // the requestContext, this can be removed. + ___legacy: { + tsvb: { + elasticsearch: legacyServer.plugins.elasticsearch, + __internals: legacyServer.newPlatform.__internals, + }, + }, + apm: plugins.apm as APMPluginContract, + }; + + const infraPluginInstance = plugin(initContext); + infraPluginInstance.setup(core, pluginDeps); + + // NP_TODO: EVERYTHING BELOW HERE IS LEGACY + + const libs = infraPluginInstance.getLibs(); + + // NP_TODO how do we replace this? Answer: return from setup function. + legacyServer.expose( + 'defineInternalSourceConfiguration', + libs.sources.defineInternalSourceConfiguration.bind(libs.sources) + ); + + // NP_TODO: How do we move this to new platform? + legacyServer.addAppLinksToSampleDataset('logs', [ { path: `/app/${APP_ID}#/logs`, label: logsSampleDataLinkLabel, diff --git a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts index d70a42473b710..f91b40815a3ae 100644 --- a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -24,7 +24,7 @@ import { const ROOT_ELEMENT_ID = 'react-infra-root'; const BREADCRUMBS_ELEMENT_ID = 'react-infra-breadcrumbs'; -export class InfraKibanaFrameworkAdapter implements InfraFrameworkAdapter { +export class KibanaFramework implements InfraFrameworkAdapter { public appState: object; public kbnVersion?: string; public timezone?: string; diff --git a/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts b/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts index 086691e665b03..9b0beb3ad519c 100644 --- a/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts +++ b/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts @@ -20,7 +20,7 @@ import { HttpLink } from 'apollo-link-http'; import { withClientState } from 'apollo-link-state'; import { InfraFrontendLibs } from '../lib'; import introspectionQueryResultData from '../../graphql/introspection.json'; -import { InfraKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; export function compose(): InfraFrontendLibs { @@ -57,7 +57,7 @@ export function compose(): InfraFrontendLibs { const infraModule = uiModules.get('app/infa'); - const framework = new InfraKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider); const libs: InfraFrontendLibs = { apolloClient, diff --git a/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts b/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts index 14fd66d378121..1e0b2f079497d 100644 --- a/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts +++ b/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts @@ -17,7 +17,7 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import ApolloClient from 'apollo-client'; import { SchemaLink } from 'apollo-link-schema'; import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools'; -import { InfraKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; import { InfraFrontendLibs } from '../lib'; @@ -27,7 +27,7 @@ export function compose(): InfraFrontendLibs { basePath: chrome.getBasePath(), xsrfToken: chrome.getXsrfToken(), }); - const framework = new InfraKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider); const typeDefs = ` Query {} `; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx index 1916e84ef21d3..7a63406bb419a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx @@ -34,11 +34,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct container filter', () => { @@ -47,11 +47,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct pod filter', () => { @@ -60,11 +60,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct position', () => { @@ -75,11 +75,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct user-defined filter', () => { @@ -92,11 +92,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct custom source id', () => { @@ -107,11 +107,11 @@ describe('RedirectToNodeLogs component', () => { ); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); }); diff --git a/x-pack/legacy/plugins/infra/server/features.ts b/x-pack/legacy/plugins/infra/server/features.ts new file mode 100644 index 0000000000000..fc20813c777b6 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/features.ts @@ -0,0 +1,65 @@ +/* + * 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'; + +export const METRICS_FEATURE = { + id: 'infrastructure', + name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', { + defaultMessage: 'Infrastructure', + }), + icon: 'infraApp', + navLinkId: 'infra:home', + app: ['infra', 'kibana'], + catalogue: ['infraops'], + privileges: { + all: { + api: ['infra'], + savedObject: { + all: ['infrastructure-ui-source'], + read: ['index-pattern'], + }, + ui: ['show', 'configureSource', 'save'], + }, + read: { + api: ['infra'], + savedObject: { + all: [], + read: ['infrastructure-ui-source', 'index-pattern'], + }, + ui: ['show'], + }, + }, +}; + +export const LOGS_FEATURE = { + id: 'logs', + name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', { + defaultMessage: 'Logs', + }), + icon: 'loggingApp', + navLinkId: 'infra:logs', + app: ['infra', 'kibana'], + catalogue: ['infralogging'], + privileges: { + all: { + api: ['infra'], + savedObject: { + all: ['infrastructure-ui-source'], + read: [], + }, + ui: ['show', 'configureSource', 'save'], + }, + read: { + api: ['infra'], + savedObject: { + all: [], + read: ['infrastructure-ui-source'], + }, + ui: ['show'], + }, + }, +}; diff --git a/x-pack/legacy/plugins/infra/server/infra_server.ts b/x-pack/legacy/plugins/infra/server/infra_server.ts index edccf5f413ab4..845e54e18c7c5 100644 --- a/x-pack/legacy/plugins/infra/server/infra_server.ts +++ b/x-pack/legacy/plugins/infra/server/infra_server.ts @@ -30,7 +30,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => { typeDefs: schemas, }); - libs.framework.registerGraphQLEndpoint('/api/infra/graphql', schema); + libs.framework.registerGraphQLEndpoint('/graphql', schema); initIpToHostName(libs); initLogAnalysisGetLogEntryRateRoute(libs); diff --git a/x-pack/legacy/plugins/infra/server/kibana.index.ts b/x-pack/legacy/plugins/infra/server/kibana.index.ts index 91bcd6be95a75..b4301b3edf367 100644 --- a/x-pack/legacy/plugins/infra/server/kibana.index.ts +++ b/x-pack/legacy/plugins/infra/server/kibana.index.ts @@ -4,97 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { Server } from 'hapi'; import JoiNamespace from 'joi'; -import { initInfraServer } from './infra_server'; -import { compose } from './lib/compose/kibana'; -import { UsageCollector } from './usage/usage_collector'; -import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view'; -import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view'; -export const initServerWithKibana = (kbnServer: Server) => { - const { usageCollection } = kbnServer.newPlatform.setup.plugins; - const libs = compose(kbnServer); - initInfraServer(libs); - - kbnServer.expose( - 'defineInternalSourceConfiguration', - libs.sources.defineInternalSourceConfiguration.bind(libs.sources) - ); - - // Register a function with server to manage the collection of usage stats - UsageCollector.registerUsageCollector(usageCollection); - - const xpackMainPlugin = kbnServer.plugins.xpack_main; - xpackMainPlugin.registerFeature({ - id: 'infrastructure', - name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', { - defaultMessage: 'Metrics', - }), - icon: 'metricsApp', - navLinkId: 'infra:home', - app: ['infra', 'kibana'], - catalogue: ['infraops'], - privileges: { - all: { - api: ['infra'], - savedObject: { - all: [ - 'infrastructure-ui-source', - inventoryViewSavedObjectType, - metricsExplorerViewSavedObjectType, - ], - read: ['index-pattern'], - }, - ui: ['show', 'configureSource', 'save'], - }, - read: { - api: ['infra'], - savedObject: { - all: [], - read: [ - 'infrastructure-ui-source', - 'index-pattern', - inventoryViewSavedObjectType, - metricsExplorerViewSavedObjectType, - ], - }, - ui: ['show'], - }, - }, - }); - - xpackMainPlugin.registerFeature({ - id: 'logs', - name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', { - defaultMessage: 'Logs', - }), - icon: 'logsApp', - navLinkId: 'infra:logs', - app: ['infra', 'kibana'], - catalogue: ['infralogging'], - privileges: { - all: { - api: ['infra'], - savedObject: { - all: ['infrastructure-ui-source'], - read: [], - }, - ui: ['show', 'configureSource', 'save'], - }, - read: { - api: ['infra'], - savedObject: { - all: [], - read: ['infrastructure-ui-source'], - }, - ui: ['show'], - }, - }, - }); -}; +export interface KbnServer extends Server { + usage: any; +} +// NP_TODO: this is only used in the root index file AFAICT, can remove after migrating to NP export const getConfigSchema = (Joi: typeof JoiNamespace) => { const InfraDefaultSourceConfigSchema = Joi.object({ metricAlias: Joi.string(), @@ -111,6 +28,7 @@ export const getConfigSchema = (Joi: typeof JoiNamespace) => { }), }); + // NP_TODO: make sure this is all represented in the NP config schema const InfraRootConfigSchema = Joi.object({ enabled: Joi.boolean().default(true), query: Joi.object({ diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/adapter_types.ts deleted file mode 100644 index b0856cf3da361..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/adapter_types.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. - */ - -export interface InfraConfigurationAdapter< - Configuration extends InfraBaseConfiguration = InfraBaseConfiguration -> { - get(): Promise; -} - -export interface InfraBaseConfiguration { - enabled: boolean; - query: { - partitionSize: number; - partitionFactor: number; - }; -} diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/inmemory_configuration_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/inmemory_configuration_adapter.ts deleted file mode 100644 index 472fa72939565..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/inmemory_configuration_adapter.ts +++ /dev/null @@ -1,16 +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 { InfraBaseConfiguration, InfraConfigurationAdapter } from './adapter_types'; - -export class InfraInmemoryConfigurationAdapter - implements InfraConfigurationAdapter { - constructor(private readonly configuration: Configuration) {} - - public async get() { - return this.configuration; - } -} diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.test.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.test.ts deleted file mode 100644 index 4d87878e9aa87..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.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 { InfraKibanaConfigurationAdapter } from './kibana_configuration_adapter'; - -describe('the InfraKibanaConfigurationAdapter', () => { - test('queries the xpack.infra configuration of the server', async () => { - const mockConfig = { - get: jest.fn(), - }; - - const configurationAdapter = new InfraKibanaConfigurationAdapter({ - config: () => mockConfig, - }); - - await configurationAdapter.get(); - - expect(mockConfig.get).toBeCalledWith('xpack.infra'); - }); - - test('applies the query defaults', async () => { - const configurationAdapter = new InfraKibanaConfigurationAdapter({ - config: () => ({ - get: () => ({}), - }), - }); - - const configuration = await configurationAdapter.get(); - - expect(configuration).toMatchObject({ - query: { - partitionSize: expect.any(Number), - partitionFactor: expect.any(Number), - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.ts deleted file mode 100644 index d3699a4820cf0..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/kibana_configuration_adapter.ts +++ /dev/null @@ -1,73 +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 Joi from 'joi'; - -import { InfraBaseConfiguration, InfraConfigurationAdapter } from './adapter_types'; - -export class InfraKibanaConfigurationAdapter implements InfraConfigurationAdapter { - private readonly server: ServerWithConfig; - - constructor(server: any) { - if (!isServerWithConfig(server)) { - throw new Error('Failed to find configuration on server.'); - } - - this.server = server; - } - - public async get() { - const config = this.server.config(); - - if (!isKibanaConfiguration(config)) { - throw new Error('Failed to access configuration of server.'); - } - - const configuration = config.get('xpack.infra') || {}; - const configurationWithDefaults: InfraBaseConfiguration = { - enabled: true, - query: { - partitionSize: 75, - partitionFactor: 1.2, - ...(configuration.query || {}), - }, - ...configuration, - }; - - // we assume this to be the configuration because Kibana would have already validated it - return configurationWithDefaults; - } -} - -interface ServerWithConfig { - config(): any; -} - -function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { - return ( - Joi.validate( - maybeServer, - Joi.object({ - config: Joi.func().required(), - }).unknown() - ).error === null - ); -} - -interface KibanaConfiguration { - get(key: string): any; -} - -function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration { - return ( - Joi.validate( - maybeConfiguration, - Joi.object({ - get: Joi.func().required(), - }).unknown() - ).error === null - ); -} diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/adapter_types.ts index 66081e60e7e10..3aaa23b378096 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/adapter_types.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraFrameworkRequest } from '../framework'; +import { RequestHandlerContext } from 'src/core/server'; export interface FieldsAdapter { getIndexFields( - req: InfraFrameworkRequest, + requestContext: RequestHandlerContext, indices: string, timefield: string ): Promise; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts index a6881a05f6f93..01306901e9caa 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts @@ -6,11 +6,9 @@ import { startsWith, uniq, first } from 'lodash'; import { idx } from '@kbn/elastic-idx'; -import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, - InfraDatabaseSearchResponse, -} from '../framework'; +import { RequestHandlerContext } from 'src/core/server'; +import { InfraDatabaseSearchResponse } from '../framework'; +import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { FieldsAdapter, IndexFieldDescriptor } from './adapter_types'; import { getAllowedListForPrefix } from '../../../../common/ecs_allowed_list'; import { getAllCompositeData } from '../../../utils/get_all_composite_data'; @@ -31,22 +29,26 @@ interface DataSetResponse { } export class FrameworkFieldsAdapter implements FieldsAdapter { - private framework: InfraBackendFrameworkAdapter; + private framework: KibanaFramework; - constructor(framework: InfraBackendFrameworkAdapter) { + constructor(framework: KibanaFramework) { this.framework = framework; } public async getIndexFields( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, indices: string, timefield: string ): Promise { - const indexPatternsService = this.framework.getIndexPatternsService(request); + const indexPatternsService = this.framework.getIndexPatternsService(requestContext); const response = await indexPatternsService.getFieldsForWildcard({ pattern: indices, }); - const { dataSets, modules } = await this.getDataSetsAndModules(request, indices, timefield); + const { dataSets, modules } = await this.getDataSetsAndModules( + requestContext, + indices, + timefield + ); const allowedList = modules.reduce( (acc, name) => uniq([...acc, ...getAllowedListForPrefix(name)]), [] as string[] @@ -59,7 +61,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { } private async getDataSetsAndModules( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, indices: string, timefield: string ): Promise<{ dataSets: string[]; modules: string[] }> { @@ -109,7 +111,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { const buckets = await getAllCompositeData( this.framework, - request, + requestContext, params, bucketSelector, handleAfterKey diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 63fded49d8222..625607c098028 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -4,91 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; -import { GraphQLSchema } from 'graphql'; -import { Lifecycle, ResponseToolkit, RouteOptions } from 'hapi'; -import { Legacy } from 'kibana'; - -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { JsonObject } from '../../../../common/typed_json'; -import { TSVBMetricModel } from '../../../../common/inventory_models/types'; - -export const internalInfraFrameworkRequest = Symbol('internalInfraFrameworkRequest'); - -/* eslint-disable @typescript-eslint/unified-signatures */ -export interface InfraBackendFrameworkAdapter { - version: string; - exposeStaticDir(urlPath: string, dir: string): void; - registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; - registerRoute( - route: InfraFrameworkRouteOptions - ): void; - callWithRequest( - req: InfraFrameworkRequest, - method: 'search', - options?: object - ): Promise>; - callWithRequest( - req: InfraFrameworkRequest, - method: 'msearch', - options?: object - ): Promise>; - callWithRequest( - req: InfraFrameworkRequest, - method: 'fieldCaps', - options?: object - ): Promise; - callWithRequest( - req: InfraFrameworkRequest, - method: 'indices.existsAlias', - options?: object - ): Promise; - callWithRequest( - req: InfraFrameworkRequest, - method: 'indices.getAlias', - options?: object - ): Promise; - callWithRequest( - req: InfraFrameworkRequest, - method: 'indices.get', - options?: object - ): Promise; - callWithRequest( - req: InfraFrameworkRequest, - method: 'ml.getBuckets', - options?: object - ): Promise; - callWithRequest( - req: InfraFrameworkRequest, - method: string, - options?: object - ): Promise; - getIndexPatternsService(req: InfraFrameworkRequest): Legacy.IndexPatternsService; - getSavedObjectsService(): Legacy.SavedObjectsService; - getSpaceId(request: InfraFrameworkRequest): string; - makeTSVBRequest( - req: InfraFrameworkRequest, - model: TSVBMetricModel, - timerange: { min: number; max: number }, - filters: JsonObject[] - ): Promise; - config(req: InfraFrameworkRequest): KibanaConfig; -} -/* eslint-enable @typescript-eslint/unified-signatures */ - -export interface InfraFrameworkRequest< - InternalRequest extends InfraWrappableRequest = InfraWrappableRequest -> { - [internalInfraFrameworkRequest]: InternalRequest; - payload: InternalRequest['payload']; - params: InternalRequest['params']; - query: InternalRequest['query']; -} - -export interface InfraWrappableRequest { - payload: Payload; - params: Params; - query: Query; +import { SearchResponse, GenericParams } from 'elasticsearch'; +import { Lifecycle } from 'hapi'; +import { ObjectType } from '@kbn/config-schema'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { RouteMethod, RouteConfig } from '../../../../../../../../src/core/server'; +import { APMPluginContract } from '../../../../../../../plugins/apm/server/plugin'; + +// NP_TODO: Compose real types from plugins we depend on, no "any" +export interface InfraServerPluginDeps { + usageCollection: UsageCollectionSetup; + spaces: any; + metrics: { + getVisData: any; + }; + indexPatterns: { + indexPatternsServiceFactory: any; + }; + features: any; + apm: APMPluginContract; + ___legacy: any; +} + +export interface CallWithRequestParams extends GenericParams { + max_concurrent_shard_requests?: number; + name?: string; + index?: string; + ignore_unavailable?: boolean; + allow_no_indices?: boolean; + size?: number; + terminate_after?: number; + fields?: string; } export type InfraResponse = Lifecycle.ReturnValue; @@ -98,22 +44,6 @@ export interface InfraFrameworkPluginOptions { options: any; } -export interface InfraFrameworkRouteOptions< - RouteRequest extends InfraWrappableRequest, - RouteResponse extends InfraResponse -> { - path: string; - method: string | string[]; - vhost?: string; - handler: InfraFrameworkRouteHandler; - options?: Pick>; -} - -export type InfraFrameworkRouteHandler< - RouteRequest extends InfraWrappableRequest, - RouteResponse extends InfraResponse -> = (request: InfraFrameworkRequest, h: ResponseToolkit) => RouteResponse; - export interface InfraDatabaseResponse { took: number; timeout: boolean; @@ -235,3 +165,12 @@ export interface InfraTSVBSeries { } export type InfraTSVBDataPoint = [number, number]; + +export type InfraRouteConfig< + params extends ObjectType, + query extends ObjectType, + body extends ObjectType, + method extends RouteMethod +> = { + method: RouteMethod; +} & RouteConfig; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/apollo_server_hapi.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/apollo_server_hapi.ts deleted file mode 100644 index da858217468f1..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/apollo_server_hapi.ts +++ /dev/null @@ -1,117 +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 * as GraphiQL from 'apollo-server-module-graphiql'; -import Boom from 'boom'; -import { Plugin, Request, ResponseToolkit, RouteOptions, Server } from 'hapi'; - -import { GraphQLOptions, runHttpQuery } from 'apollo-server-core'; - -export type HapiOptionsFunction = (req: Request) => GraphQLOptions | Promise; - -export interface HapiGraphQLPluginOptions { - path: string; - vhost?: string; - route?: RouteOptions; - graphqlOptions: GraphQLOptions | HapiOptionsFunction; -} - -export const graphqlHapi: Plugin = { - name: 'graphql', - register: (server: Server, options: HapiGraphQLPluginOptions) => { - if (!options || !options.graphqlOptions) { - throw new Error('Apollo Server requires options.'); - } - - server.route({ - options: options.route || {}, - handler: async (request: Request, h: ResponseToolkit) => { - try { - const query = - request.method === 'post' - ? (request.payload as Record) - : (request.query as Record); - - const gqlResponse = await runHttpQuery([request], { - method: request.method.toUpperCase(), - options: options.graphqlOptions, - query, - }); - - return h.response(gqlResponse).type('application/json'); - } catch (error) { - if ('HttpQueryError' !== error.name) { - const queryError = Boom.boomify(error); - - queryError.output.payload.message = error.message; - - return queryError; - } - - if (error.isGraphQLError === true) { - return h - .response(error.message) - .code(error.statusCode) - .type('application/json'); - } - - const genericError = new Boom(error.message, { statusCode: error.statusCode }); - - if (error.headers) { - Object.keys(error.headers).forEach(header => { - genericError.output.headers[header] = error.headers[header]; - }); - } - - // Boom hides the error when status code is 500 - - genericError.output.payload.message = error.message; - - throw genericError; - } - }, - method: ['GET', 'POST'], - path: options.path || '/graphql', - vhost: options.vhost || undefined, - }); - }, -}; - -export type HapiGraphiQLOptionsFunction = ( - req?: Request -) => GraphiQL.GraphiQLData | Promise; - -export interface HapiGraphiQLPluginOptions { - path: string; - - route?: any; - - graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction; -} - -export const graphiqlHapi: Plugin = { - name: 'graphiql', - register: (server: Server, options: HapiGraphiQLPluginOptions) => { - if (!options || !options.graphiqlOptions) { - throw new Error('Apollo Server GraphiQL requires options.'); - } - - server.route({ - options: options.route || {}, - handler: async (request: Request, h: ResponseToolkit) => { - const graphiqlString = await GraphiQL.resolveGraphiQLString( - request.query, - options.graphiqlOptions, - request - ); - - return h.response(graphiqlString).type('text/html'); - }, - method: 'GET', - path: options.path || '/graphiql', - }); - }, -}; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index e96f1687bbb2e..19121d92f02c9 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -4,116 +4,207 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable @typescript-eslint/array-type */ + import { GenericParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { Legacy } from 'kibana'; - -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { get } from 'lodash'; +import { runHttpQuery } from 'apollo-server-core'; +import { schema, TypeOf, ObjectType } from '@kbn/config-schema'; import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, - InfraFrameworkRouteOptions, - InfraResponse, + InfraRouteConfig, InfraTSVBResponse, - InfraWrappableRequest, - internalInfraFrameworkRequest, + InfraServerPluginDeps, + CallWithRequestParams, + InfraDatabaseSearchResponse, + InfraDatabaseMultiResponse, + InfraDatabaseFieldCapsResponse, + InfraDatabaseGetIndicesResponse, + InfraDatabaseGetIndicesAliasResponse, } from './adapter_types'; -import { - graphiqlHapi, - graphqlHapi, - HapiGraphiQLPluginOptions, - HapiGraphQLPluginOptions, -} from './apollo_server_hapi'; import { TSVBMetricModel } from '../../../../common/inventory_models/types'; +import { + CoreSetup, + IRouter, + KibanaRequest, + RequestHandlerContext, + KibanaResponseFactory, + RouteMethod, +} from '../../../../../../../../src/core/server'; +import { RequestHandler } from '../../../../../../../../src/core/server'; +import { InfraConfig } from '../../../../../../../plugins/infra/server'; -interface CallWithRequestParams extends GenericParams { - max_concurrent_shard_requests?: number; -} - -export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFrameworkAdapter { - public version: string; +export class KibanaFramework { + public router: IRouter; + private core: CoreSetup; + public plugins: InfraServerPluginDeps; - constructor(private server: Legacy.Server) { - this.version = server.config().get('pkg.version'); + constructor(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) { + this.router = core.http.createRouter(); + this.core = core; + this.plugins = plugins; } - public config(req: InfraFrameworkRequest): KibanaConfig { - const internalRequest = req[internalInfraFrameworkRequest]; - return internalRequest.server.config(); + public registerRoute< + params extends ObjectType = any, + query extends ObjectType = any, + body extends ObjectType = any, + method extends RouteMethod = any + >( + config: InfraRouteConfig, + handler: RequestHandler + ) { + const defaultOptions = { + tags: ['access:infra'], + }; + const routeConfig = { + path: config.path, + validate: config.validate, + // Currently we have no use of custom options beyond tags, this can be extended + // beyond defaultOptions if it's needed. + options: defaultOptions, + }; + switch (config.method) { + case 'get': + this.router.get(routeConfig, handler); + break; + case 'post': + this.router.post(routeConfig, handler); + break; + case 'delete': + this.router.delete(routeConfig, handler); + break; + case 'put': + this.router.put(routeConfig, handler); + break; + } } - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, - }); - } + public registerGraphQLEndpoint(routePath: string, gqlSchema: GraphQLSchema) { + // These endpoints are validated by GraphQL at runtime and with GraphQL generated types + const body = schema.object({}, { allowUnknowns: true }); + type Body = TypeOf; - public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { - this.server.register({ - options: { - graphqlOptions: (req: Legacy.Request) => ({ - context: { req: wrapRequest(req) }, - schema, - }), - path: routePath, - route: { - tags: ['access:infra'], - }, + const routeOptions = { + path: `/api/infra${routePath}`, + validate: { + body, }, - plugin: graphqlHapi, - }); - - this.server.register({ options: { - graphiqlOptions: request => ({ - endpointURL: request ? `${request.getBasePath()}${routePath}` : routePath, - passHeader: `'kbn-version': '${this.version}'`, - }), - path: `${routePath}/graphiql`, - route: { - tags: ['access:infra'], - }, + tags: ['access:infra'], }, - plugin: graphiqlHapi, - }); - } + }; + async function handler( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + try { + const query = + request.route.method === 'post' + ? (request.body as Record) + : (request.query as Record); - public registerRoute< - RouteRequest extends InfraWrappableRequest, - RouteResponse extends InfraResponse - >(route: InfraFrameworkRouteOptions) { - const wrappedHandler = (request: any, h: Legacy.ResponseToolkit) => - route.handler(wrapRequest(request), h); - - this.server.route({ - handler: wrappedHandler, - options: route.options, - method: route.method, - path: route.path, - }); + const gqlResponse = await runHttpQuery([context, request], { + method: request.route.method.toUpperCase(), + options: (req: RequestHandlerContext, rawReq: KibanaRequest) => ({ + context: { req, rawReq }, + schema: gqlSchema, + }), + query, + }); + + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, + }); + } catch (error) { + const errorBody = { + message: error.message, + }; + + if ('HttpQueryError' !== error.name) { + return response.internalError({ + body: errorBody, + }); + } + + if (error.isGraphQLError === true) { + return response.customError({ + statusCode: error.statusCode, + body: errorBody, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + const { headers = [], statusCode = 500 } = error; + return response.customError({ + statusCode, + headers, + body: errorBody, + }); + + // NP_TODO: Do we still need to re-throw this error in this case? if we do, can we + // still call the response.customError method to control the HTTP response? + // throw error; + } + } + this.router.post(routeOptions, handler); + this.router.get(routeOptions, handler); } - public async callWithRequest( - req: InfraFrameworkRequest, + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: 'search', + options?: CallWithRequestParams + ): Promise>; + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: 'msearch', + options?: CallWithRequestParams + ): Promise>; + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: 'fieldCaps', + options?: CallWithRequestParams + ): Promise; + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: 'indices.existsAlias', + options?: CallWithRequestParams + ): Promise; + callWithRequest( + requestContext: RequestHandlerContext, + method: 'indices.getAlias', + options?: object + ): Promise; + callWithRequest( + requestContext: RequestHandlerContext, + method: 'indices.get' | 'ml.getBuckets', + options?: object + ): Promise; + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: string, + options?: CallWithRequestParams + ): Promise; + + public async callWithRequest( + requestContext: RequestHandlerContext, endpoint: string, - params: CallWithRequestParams, - ...rest: any[] + params: CallWithRequestParams ) { - const internalRequest = req[internalInfraFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen'); + const { elasticsearch, uiSettings } = requestContext.core; + + const includeFrozen = await uiSettings.client.get('search:includeFrozen'); if (endpoint === 'msearch') { - const maxConcurrentShardRequests = await internalRequest - .getUiSettingsService() - .get('courier:maxConcurrentShardRequests'); + const maxConcurrentShardRequests = await uiSettings.client.get( + 'courier:maxConcurrentShardRequests' + ); if (maxConcurrentShardRequests > 0) { params = { ...params, max_concurrent_shard_requests: maxConcurrentShardRequests }; } @@ -125,95 +216,79 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework } : {}; - const fields = await callWithRequest( - internalRequest, - endpoint, - { - ...params, - ...frozenIndicesParams, - }, - ...rest - ); - return fields; + return elasticsearch.dataClient.callAsCurrentUser(endpoint, { + ...params, + ...frozenIndicesParams, + }); } public getIndexPatternsService( - request: InfraFrameworkRequest + requestContext: RequestHandlerContext ): Legacy.IndexPatternsService { - return this.server.indexPatternsServiceFactory({ + return this.plugins.indexPatterns.indexPatternsServiceFactory({ callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => { - const fieldCaps = await this.callWithRequest( - request, - method, - { ...args, allowNoIndices: true } as GenericParams, - ...rest - ); + const fieldCaps = await this.callWithRequest(requestContext, method, { + ...args, + allowNoIndices: true, + } as GenericParams); return fieldCaps; }, }); } - public getSpaceId(request: InfraFrameworkRequest): string { - const spacesPlugin = this.server.plugins.spaces; + public getSpaceId(request: KibanaRequest): string { + const spacesPlugin = this.plugins.spaces; - if (spacesPlugin && typeof spacesPlugin.getSpaceId === 'function') { - return spacesPlugin.getSpaceId(request[internalInfraFrameworkRequest]); + if ( + spacesPlugin && + spacesPlugin.spacesService && + typeof spacesPlugin.spacesService.getSpaceId === 'function' + ) { + return spacesPlugin.spacesService.getSpaceId(request); } else { return 'default'; } } - public getSavedObjectsService() { - return this.server.savedObjects; - } - + // NP_TODO: This method needs to no longer require full KibanaRequest public async makeTSVBRequest( - req: InfraFrameworkRequest, + request: KibanaRequest, model: TSVBMetricModel, timerange: { min: number; max: number }, - filters: any[] - ) { - const internalRequest = req[internalInfraFrameworkRequest]; - const server = internalRequest.server; - const getVisData = get(server, 'plugins.metrics.getVisData'); + filters: any[], + requestContext: RequestHandlerContext + ): Promise { + const { getVisData } = this.plugins.metrics; if (typeof getVisData !== 'function') { throw new Error('TSVB is not available'); } - - // getBasePath returns randomized base path AND spaces path - const basePath = internalRequest.getBasePath(); - const url = `${basePath}/api/metrics/vis/data`; - + const url = this.core.http.basePath.prepend('/api/metrics/vis/data'); // For the following request we need a copy of the instnace of the internal request // but modified for our TSVB request. This will ensure all the instance methods // are available along with our overriden values - const request = Object.assign( - Object.create(Object.getPrototypeOf(internalRequest)), - internalRequest, - { - url, - method: 'POST', - payload: { - timerange, - panels: [model], - filters, + const requestCopy = Object.assign({}, request, { + url, + method: 'POST', + payload: { + timerange, + panels: [model], + filters, + }, + // NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that + // originate from the New Platform router (and are very different to the old request object). + // Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just + // the requestContext, this can be removed. + server: { + plugins: { + elasticsearch: this.plugins.___legacy.tsvb.elasticsearch, }, - } - ); - const result = await getVisData(request); - return result as InfraTSVBResponse; + newPlatform: { + __internals: this.plugins.___legacy.tsvb.__internals, + }, + }, + getUiSettingsService: () => requestContext.core.uiSettings.client, + getSavedObjectsClient: () => requestContext.core.savedObjects.client, + }); + return getVisData(requestCopy); } } - -export function wrapRequest( - req: InternalRequest -): InfraFrameworkRequest { - const { params, payload, query } = req; - - return { - [internalInfraFrameworkRequest]: req, - params, - payload, - query, - }; -} diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 547e74eecb67c..ec45171baa7b0 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -15,6 +15,7 @@ import zip from 'lodash/fp/zip'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity, constant } from 'fp-ts/lib/function'; +import { RequestHandlerContext } from 'src/core/server'; import { compareTimeKeys, isTimeKey, TimeKey } from '../../../../common/time'; import { JsonObject } from '../../../../common/typed_json'; import { @@ -24,8 +25,8 @@ import { LogSummaryBucket, } from '../../domains/log_entries_domain'; import { InfraSourceConfiguration } from '../../sources'; -import { InfraFrameworkRequest, SortedSearchHit } from '../framework'; -import { InfraBackendFrameworkAdapter } from '../framework'; +import { SortedSearchHit } from '../framework'; +import { KibanaFramework } from '../framework/kibana_framework_adapter'; const DAY_MILLIS = 24 * 60 * 60 * 1000; const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000, Infinity].map(days => days * DAY_MILLIS); @@ -39,10 +40,10 @@ interface LogItemHit { } export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { - constructor(private readonly framework: InfraBackendFrameworkAdapter) {} + constructor(private readonly framework: KibanaFramework) {} public async getAdjacentLogEntryDocuments( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], start: TimeKey, @@ -64,7 +65,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } const documentsInInterval = await this.getLogEntryDocumentsBetween( - request, + requestContext, sourceConfiguration, fields, intervalStart, @@ -82,7 +83,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } public async getContainedLogEntryDocuments( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], start: TimeKey, @@ -91,7 +92,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { highlightQuery?: LogEntryQuery ): Promise { const documents = await this.getLogEntryDocumentsBetween( - request, + requestContext, sourceConfiguration, fields, start.time, @@ -106,7 +107,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } public async getContainedLogSummaryBuckets( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, start: number, end: number, @@ -165,7 +166,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { }, }; - const response = await this.framework.callWithRequest(request, 'search', query); + const response = await this.framework.callWithRequest(requestContext, 'search', query); return pipe( LogSummaryResponseRuntimeType.decode(response), @@ -179,12 +180,12 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } public async getLogItem( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, id: string, sourceConfiguration: InfraSourceConfiguration ) { const search = (searchOptions: object) => - this.framework.callWithRequest(request, 'search', searchOptions); + this.framework.callWithRequest(requestContext, 'search', searchOptions); const params = { index: sourceConfiguration.logAlias, @@ -212,7 +213,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { } private async getLogEntryDocumentsBetween( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], start: number, @@ -298,7 +299,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { }; const response = await this.framework.callWithRequest( - request, + requestContext, 'search', query ); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts index adb8c811ed57d..acd7a2528bb42 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; import { InfraMetric, InfraMetricData, InfraNodeType, InfraTimerangeInput, } from '../../../graphql/types'; - import { InfraSourceConfiguration } from '../../sources'; -import { InfraFrameworkRequest } from '../framework'; export interface InfraMetricsRequestOptions { nodeIds: { @@ -27,8 +26,9 @@ export interface InfraMetricsRequestOptions { export interface InfraMetricsAdapter { getMetrics( - req: InfraFrameworkRequest, - options: InfraMetricsRequestOptions + requestContext: RequestHandlerContext, + options: InfraMetricsRequestOptions, + request: KibanaRequest // NP_TODO: temporarily needed until metrics getVisData no longer needs full request ): Promise; } diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index 331abd4ffb35a..db3c516841cd4 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -6,10 +6,9 @@ import { i18n } from '@kbn/i18n'; import { flatten, get } from 'lodash'; - -import Boom from 'boom'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { InfraMetric, InfraMetricData, InfraNodeType } from '../../../graphql/types'; -import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../framework'; +import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from './adapter_types'; import { checkValidNode } from './lib/check_valid_node'; import { metrics } from '../../../../common/inventory_models'; @@ -17,15 +16,16 @@ import { TSVBMetricModelCreator } from '../../../../common/inventory_models/type import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; export class KibanaMetricsAdapter implements InfraMetricsAdapter { - private framework: InfraBackendFrameworkAdapter; + private framework: KibanaFramework; - constructor(framework: InfraBackendFrameworkAdapter) { + constructor(framework: KibanaFramework) { this.framework = framework; } public async getMetrics( - req: InfraFrameworkRequest, - options: InfraMetricsRequestOptions + requestContext: RequestHandlerContext, + options: InfraMetricsRequestOptions, + rawRequest: KibanaRequest // NP_TODO: Temporarily needed until metrics getVisData no longer needs full request ): Promise { const fields = { [InfraNodeType.host]: options.sourceConfiguration.fields.host, @@ -35,11 +35,11 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { const indexPattern = `${options.sourceConfiguration.metricAlias},${options.sourceConfiguration.logAlias}`; const nodeField = fields[options.nodeType]; const search = (searchOptions: object) => - this.framework.callWithRequest<{}, Aggregation>(req, 'search', searchOptions); + this.framework.callWithRequest<{}, Aggregation>(requestContext, 'search', searchOptions); const validNode = await checkValidNode(search, indexPattern, nodeField, options.nodeIds.nodeId); if (!validNode) { - throw Boom.notFound( + throw new Error( i18n.translate('xpack.infra.kibanaMetrics.nodeDoesNotExistErrorMessage', { defaultMessage: '{nodeId} does not exist.', values: { @@ -50,7 +50,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { } const requests = options.metrics.map(metricId => - this.makeTSVBRequest(metricId, options, req, nodeField) + this.makeTSVBRequest(metricId, options, rawRequest, nodeField, requestContext) ); return Promise.all(requests) @@ -92,12 +92,13 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { async makeTSVBRequest( metricId: InfraMetric, options: InfraMetricsRequestOptions, - req: InfraFrameworkRequest, - nodeField: string + req: KibanaRequest, + nodeField: string, + requestContext: RequestHandlerContext ) { const createTSVBModel = get(metrics, ['tsvb', metricId]) as TSVBMetricModelCreator | undefined; if (!createTSVBModel) { - throw Boom.badRequest( + throw new Error( i18n.translate('xpack.infra.metrics.missingTSVBModelError', { defaultMessage: 'The TSVB model for {metricId} does not exist for {nodeType}', values: { @@ -121,7 +122,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { ); const calculatedInterval = await calculateMetricInterval( this.framework, - req, + requestContext, { indexPattern: `${options.sourceConfiguration.logAlias},${options.sourceConfiguration.metricAlias}`, timestampField: options.sourceConfiguration.fields.timestamp, @@ -135,7 +136,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { } if (model.id_type === 'cloud' && !options.nodeIds.cloudId) { - throw Boom.badRequest( + throw new Error( i18n.translate('xpack.infra.kibanaMetrics.cloudIdMissingErrorMessage', { defaultMessage: 'Model for {metricId} requires a cloudId, but none was given for {nodeId}.', @@ -152,6 +153,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { ? [{ match: { [model.map_field_to]: id } }] : [{ match: { [nodeField]: id } }]; - return this.framework.makeTSVBRequest(req, model, timerange, filters); + return this.framework.makeTSVBRequest(req, model, timerange, filters, requestContext); } } diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts index e66da3f3fa6cb..635f6ff9762c5 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts @@ -4,26 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext } from 'src/core/server'; import { InfraSourceStatusAdapter } from '../../source_status'; -import { - InfraBackendFrameworkAdapter, - InfraDatabaseGetIndicesResponse, - InfraFrameworkRequest, -} from '../framework'; +import { InfraDatabaseGetIndicesResponse } from '../framework'; +import { KibanaFramework } from '../framework/kibana_framework_adapter'; export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusAdapter { - constructor(private readonly framework: InfraBackendFrameworkAdapter) {} + constructor(private readonly framework: KibanaFramework) {} - public async getIndexNames(request: InfraFrameworkRequest, aliasName: string) { + public async getIndexNames(requestContext: RequestHandlerContext, aliasName: string) { const indexMaps = await Promise.all([ this.framework - .callWithRequest(request, 'indices.getAlias', { + .callWithRequest(requestContext, 'indices.getAlias', { name: aliasName, filterPath: '*.settings.index.uuid', // to keep the response size as small as possible }) .catch(withDefaultIfNotFound({})), this.framework - .callWithRequest(request, 'indices.get', { + .callWithRequest(requestContext, 'indices.get', { index: aliasName, filterPath: '*.settings.index.uuid', // to keep the response size as small as possible }) @@ -36,15 +34,15 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA ); } - public async hasAlias(request: InfraFrameworkRequest, aliasName: string) { - return await this.framework.callWithRequest(request, 'indices.existsAlias', { + public async hasAlias(requestContext: RequestHandlerContext, aliasName: string) { + return await this.framework.callWithRequest(requestContext, 'indices.existsAlias', { name: aliasName, }); } - public async hasIndices(request: InfraFrameworkRequest, indexNames: string) { + public async hasIndices(requestContext: RequestHandlerContext, indexNames: string) { return await this.framework - .callWithRequest(request, 'search', { + .callWithRequest(requestContext, 'search', { ignore_unavailable: true, allow_no_indices: true, index: indexNames, diff --git a/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts index 215c41bcf6b7c..305841aa52d36 100644 --- a/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts @@ -3,12 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { Server } from 'hapi'; - -import { InfraKibanaConfigurationAdapter } from '../adapters/configuration/kibana_configuration_adapter'; import { FrameworkFieldsAdapter } from '../adapters/fields/framework_fields_adapter'; -import { InfraKibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { InfraKibanaLogEntriesAdapter } from '../adapters/log_entries/kibana_log_entries_adapter'; import { KibanaMetricsAdapter } from '../adapters/metrics/kibana_metrics_adapter'; import { InfraElasticsearchSourceStatusAdapter } from '../adapters/source_status'; @@ -20,13 +16,14 @@ import { InfraLogAnalysis } from '../log_analysis'; import { InfraSnapshot } from '../snapshot'; import { InfraSourceStatus } from '../source_status'; import { InfraSources } from '../sources'; +import { InfraConfig } from '../../../../../../plugins/infra/server'; +import { CoreSetup } from '../../../../../../../src/core/server'; +import { InfraServerPluginDeps } from '../adapters/framework/adapter_types'; -export function compose(server: Server): InfraBackendLibs { - const configuration = new InfraKibanaConfigurationAdapter(server); - const framework = new InfraKibanaBackendFrameworkAdapter(server); +export function compose(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) { + const framework = new KibanaFramework(core, config, plugins); const sources = new InfraSources({ - configuration, - savedObjects: framework.getSavedObjectsService(), + config, }); const sourceStatus = new InfraSourceStatus(new InfraElasticsearchSourceStatusAdapter(framework), { sources, @@ -34,6 +31,7 @@ export function compose(server: Server): InfraBackendLibs { const snapshot = new InfraSnapshot({ sources, framework }); const logAnalysis = new InfraLogAnalysis({ framework }); + // TODO: separate these out individually and do away with "domains" as a temporary group const domainLibs: InfraDomainLibs = { fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { sources, @@ -45,7 +43,7 @@ export function compose(server: Server): InfraBackendLibs { }; const libs: InfraBackendLibs = { - configuration, + configuration: config, // NP_TODO: Do we ever use this anywhere? framework, logAnalysis, snapshot, diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/fields_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/fields_domain.ts index c5a3bbeb87449..a00c76216da4c 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/fields_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/fields_domain.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext } from 'src/core/server'; import { InfraIndexField, InfraIndexType } from '../../graphql/types'; import { FieldsAdapter } from '../adapters/fields'; -import { InfraFrameworkRequest } from '../adapters/framework'; import { InfraSources } from '../sources'; export class InfraFieldsDomain { @@ -16,16 +16,19 @@ export class InfraFieldsDomain { ) {} public async getFields( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, indexType: InfraIndexType ): Promise { - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const includeMetricIndices = [InfraIndexType.ANY, InfraIndexType.METRICS].includes(indexType); const includeLogIndices = [InfraIndexType.ANY, InfraIndexType.LOGS].includes(indexType); const fields = await this.adapter.getIndexFields( - request, + requestContext, `${includeMetricIndices ? configuration.metricAlias : ''},${ includeLogIndices ? configuration.logAlias : '' }`, diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 0127f80b31357..597073b1e901f 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -7,6 +7,7 @@ import stringify from 'json-stable-stringify'; import { sortBy } from 'lodash'; +import { RequestHandlerContext } from 'src/core/server'; import { TimeKey } from '../../../../common/time'; import { JsonObject } from '../../../../common/typed_json'; import { @@ -16,7 +17,6 @@ import { InfraLogSummaryBucket, InfraLogSummaryHighlightBucket, } from '../../../graphql/types'; -import { InfraFrameworkRequest } from '../../adapters/framework'; import { InfraSourceConfiguration, InfraSources, @@ -40,7 +40,7 @@ export class InfraLogEntriesDomain { ) {} public async getLogEntriesAround( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, key: TimeKey, maxCountBefore: number, @@ -55,14 +55,17 @@ export class InfraLogEntriesDomain { }; } - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const messageFormattingRules = compileFormattingRules( getBuiltinRules(configuration.fields.message) ); const requiredFields = getRequiredFields(configuration, messageFormattingRules); const documentsBefore = await this.adapter.getAdjacentLogEntryDocuments( - request, + requestContext, configuration, requiredFields, key, @@ -80,7 +83,7 @@ export class InfraLogEntriesDomain { }; const documentsAfter = await this.adapter.getAdjacentLogEntryDocuments( - request, + requestContext, configuration, requiredFields, lastKeyBefore, @@ -101,20 +104,23 @@ export class InfraLogEntriesDomain { } public async getLogEntriesBetween( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, startKey: TimeKey, endKey: TimeKey, filterQuery?: LogEntryQuery, highlightQuery?: LogEntryQuery ): Promise { - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const messageFormattingRules = compileFormattingRules( getBuiltinRules(configuration.fields.message) ); const requiredFields = getRequiredFields(configuration, messageFormattingRules); const documents = await this.adapter.getContainedLogEntryDocuments( - request, + requestContext, configuration, requiredFields, startKey, @@ -129,7 +135,7 @@ export class InfraLogEntriesDomain { } public async getLogEntryHighlights( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, startKey: TimeKey, endKey: TimeKey, @@ -140,7 +146,10 @@ export class InfraLogEntriesDomain { }>, filterQuery?: LogEntryQuery ): Promise { - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const messageFormattingRules = compileFormattingRules( getBuiltinRules(configuration.fields.message) ); @@ -158,7 +167,7 @@ export class InfraLogEntriesDomain { : highlightQuery; const [documentsBefore, documents, documentsAfter] = await Promise.all([ this.adapter.getAdjacentLogEntryDocuments( - request, + requestContext, configuration, requiredFields, startKey, @@ -168,7 +177,7 @@ export class InfraLogEntriesDomain { highlightQuery ), this.adapter.getContainedLogEntryDocuments( - request, + requestContext, configuration, requiredFields, startKey, @@ -177,7 +186,7 @@ export class InfraLogEntriesDomain { highlightQuery ), this.adapter.getAdjacentLogEntryDocuments( - request, + requestContext, configuration, requiredFields, endKey, @@ -203,16 +212,19 @@ export class InfraLogEntriesDomain { } public async getLogSummaryBucketsBetween( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, start: number, end: number, bucketSize: number, filterQuery?: LogEntryQuery ): Promise { - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( - request, + requestContext, configuration, start, end, @@ -223,7 +235,7 @@ export class InfraLogEntriesDomain { } public async getLogSummaryHighlightBucketsBetween( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, start: number, end: number, @@ -231,7 +243,10 @@ export class InfraLogEntriesDomain { highlightQueries: string[], filterQuery?: LogEntryQuery ): Promise { - const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId); + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const messageFormattingRules = compileFormattingRules( getBuiltinRules(configuration.fields.message) ); @@ -248,7 +263,7 @@ export class InfraLogEntriesDomain { } : highlightQuery; const summaryBuckets = await this.adapter.getContainedLogSummaryBuckets( - request, + requestContext, configuration, start, end, @@ -266,11 +281,11 @@ export class InfraLogEntriesDomain { } public async getLogItem( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, id: string, sourceConfiguration: InfraSourceConfiguration ): Promise { - const document = await this.adapter.getLogItem(request, id, sourceConfiguration); + const document = await this.adapter.getLogItem(requestContext, id, sourceConfiguration); const defaultFields = [ { field: '_index', value: document._index }, { field: '_id', value: document._id }, @@ -300,7 +315,7 @@ interface LogItemHit { export interface LogEntriesAdapter { getAdjacentLogEntryDocuments( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], start: TimeKey, @@ -311,7 +326,7 @@ export interface LogEntriesAdapter { ): Promise; getContainedLogEntryDocuments( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, fields: string[], start: TimeKey, @@ -321,7 +336,7 @@ export interface LogEntriesAdapter { ): Promise; getContainedLogSummaryBuckets( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, start: number, end: number, @@ -330,7 +345,7 @@ export interface LogEntriesAdapter { ): Promise; getLogItem( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, id: string, source: InfraSourceConfiguration ): Promise; diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts index 862ca8b4c823f..5d7d54a6a2e50 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { InfraMetricData } from '../../graphql/types'; -import { InfraFrameworkRequest } from '../adapters/framework/adapter_types'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from '../adapters/metrics/adapter_types'; export class InfraMetricsDomain { @@ -16,9 +16,10 @@ export class InfraMetricsDomain { } public async getMetrics( - req: InfraFrameworkRequest, - options: InfraMetricsRequestOptions + requestContext: RequestHandlerContext, + options: InfraMetricsRequestOptions, + rawRequest: KibanaRequest // NP_TODO: temporarily needed until metrics getVisData no longer needs full request ): Promise { - return await this.adapter.getMetrics(req, options); + return await this.adapter.getMetrics(requestContext, options, rawRequest); } } diff --git a/x-pack/legacy/plugins/infra/server/lib/infra_types.ts b/x-pack/legacy/plugins/infra/server/lib/infra_types.ts index b436bb7e4fe58..46d32885600df 100644 --- a/x-pack/legacy/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/infra_types.ts @@ -5,8 +5,6 @@ */ import { InfraSourceConfiguration } from '../../public/graphql/types'; -import { InfraConfigurationAdapter } from './adapters/configuration'; -import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from './adapters/framework'; import { InfraFieldsDomain } from './domains/fields_domain'; import { InfraLogEntriesDomain } from './domains/log_entries_domain'; import { InfraMetricsDomain } from './domains/metrics_domain'; @@ -14,6 +12,15 @@ import { InfraLogAnalysis } from './log_analysis/log_analysis'; import { InfraSnapshot } from './snapshot'; import { InfraSources } from './sources'; import { InfraSourceStatus } from './source_status'; +import { InfraConfig } from '../../../../../plugins/infra/server'; +import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; + +// NP_TODO: We shouldn't need this context anymore but I am +// not sure how the graphql stuff uses it, so we can't remove it yet +export interface InfraContext { + req: any; + rawReq?: any; +} export interface InfraDomainLibs { fields: InfraFieldsDomain; @@ -22,8 +29,8 @@ export interface InfraDomainLibs { } export interface InfraBackendLibs extends InfraDomainLibs { - configuration: InfraConfigurationAdapter; - framework: InfraBackendFrameworkAdapter; + configuration: InfraConfig; + framework: KibanaFramework; logAnalysis: InfraLogAnalysis; snapshot: InfraSnapshot; sources: InfraSources; @@ -40,7 +47,3 @@ export interface InfraConfiguration { default: InfraSourceConfiguration; }; } - -export interface InfraContext { - req: InfraFrameworkRequest; -} diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts index d970a142c5c23..fac49a7980f26 100644 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts @@ -9,7 +9,7 @@ import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { getJobId } from '../../../common/log_analysis'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; -import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../adapters/framework'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { NoLogRateResultsIndexError } from './errors'; import { logRateModelPlotResponseRT, @@ -17,37 +17,38 @@ import { LogRateModelPlotBucket, CompositeTimestampPartitionKey, } from './queries'; +import { RequestHandlerContext, KibanaRequest } from '../../../../../../../src/core/server'; const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; export class InfraLogAnalysis { constructor( private readonly libs: { - framework: InfraBackendFrameworkAdapter; + framework: KibanaFramework; } ) {} - public getJobIds(request: InfraFrameworkRequest, sourceId: string) { + public getJobIds(request: KibanaRequest, sourceId: string) { return { logEntryRate: getJobId(this.libs.framework.getSpaceId(request), sourceId, 'log-entry-rate'), }; } public async getLogEntryRateBuckets( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, startTime: number, endTime: number, - bucketDuration: number + bucketDuration: number, + request: KibanaRequest ) { const logRateJobId = this.getJobIds(request, sourceId).logEntryRate; - let mlModelPlotBuckets: LogRateModelPlotBucket[] = []; let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined; while (true) { const mlModelPlotResponse = await this.libs.framework.callWithRequest( - request, + requestContext, 'search', createLogEntryRateQuery( logRateJobId, diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts index 741293f61056e..59a4e8911a94d 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts @@ -5,6 +5,7 @@ */ import { idx } from '@kbn/elastic-idx'; +import { RequestHandlerContext } from 'src/core/server'; import { InfraSnapshotGroupbyInput, InfraSnapshotMetricInput, @@ -13,11 +14,8 @@ import { InfraNodeType, InfraSourceConfiguration, } from '../../graphql/types'; -import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, - InfraDatabaseSearchResponse, -} from '../adapters/framework'; +import { InfraDatabaseSearchResponse } from '../adapters/framework'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { InfraSources } from '../sources'; import { JsonObject } from '../../../common/typed_json'; @@ -49,20 +47,18 @@ export interface InfraSnapshotRequestOptions { } export class InfraSnapshot { - constructor( - private readonly libs: { sources: InfraSources; framework: InfraBackendFrameworkAdapter } - ) {} + constructor(private readonly libs: { sources: InfraSources; framework: KibanaFramework }) {} public async getNodes( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, options: InfraSnapshotRequestOptions ): Promise { // Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch // in order to page through the results of their respective composite aggregations. // Both chains of requests are supposed to run in parallel, and their results be merged // when they have both been completed. - const groupedNodesPromise = requestGroupedNodes(request, options, this.libs.framework); - const nodeMetricsPromise = requestNodeMetrics(request, options, this.libs.framework); + const groupedNodesPromise = requestGroupedNodes(requestContext, options, this.libs.framework); + const nodeMetricsPromise = requestNodeMetrics(requestContext, options, this.libs.framework); const groupedNodeBuckets = await groupedNodesPromise; const nodeMetricBuckets = await nodeMetricsPromise; @@ -79,9 +75,9 @@ const handleAfterKey = createAfterKeyHandler('body.aggregations.nodes.composite. ); const requestGroupedNodes = async ( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, options: InfraSnapshotRequestOptions, - framework: InfraBackendFrameworkAdapter + framework: KibanaFramework ): Promise => { const query = { allowNoIndices: true, @@ -130,13 +126,13 @@ const requestGroupedNodes = async ( return await getAllCompositeData< InfraSnapshotAggregationResponse, InfraSnapshotNodeGroupByBucket - >(framework, request, query, bucketSelector, handleAfterKey); + >(framework, requestContext, query, bucketSelector, handleAfterKey); }; const requestNodeMetrics = async ( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, options: InfraSnapshotRequestOptions, - framework: InfraBackendFrameworkAdapter + framework: KibanaFramework ): Promise => { const index = options.metric.type === 'logRate' @@ -191,7 +187,7 @@ const requestNodeMetrics = async ( return await getAllCompositeData< InfraSnapshotAggregationResponse, InfraSnapshotNodeMetricsBucket - >(framework, request, query, bucketSelector, handleAfterKey); + >(framework, requestContext, query, bucketSelector, handleAfterKey); }; // buckets can be InfraSnapshotNodeGroupByBucket[] or InfraSnapshotNodeMetricsBucket[] diff --git a/x-pack/legacy/plugins/infra/server/lib/source_status.ts b/x-pack/legacy/plugins/infra/server/lib/source_status.ts index f9f37b5aa9e5a..1f0845b6b223f 100644 --- a/x-pack/legacy/plugins/infra/server/lib/source_status.ts +++ b/x-pack/legacy/plugins/infra/server/lib/source_status.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraFrameworkRequest } from './adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; import { InfraSources } from './sources'; export class InfraSourceStatus { @@ -14,58 +14,85 @@ export class InfraSourceStatus { ) {} public async getLogIndexNames( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string ): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const indexNames = await this.adapter.getIndexNames( - request, + requestContext, sourceConfiguration.configuration.logAlias ); return indexNames; } public async getMetricIndexNames( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string ): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const indexNames = await this.adapter.getIndexNames( - request, + requestContext, sourceConfiguration.configuration.metricAlias ); return indexNames; } - public async hasLogAlias(request: InfraFrameworkRequest, sourceId: string): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + public async hasLogAlias( + requestContext: RequestHandlerContext, + sourceId: string + ): Promise { + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const hasAlias = await this.adapter.hasAlias( - request, + requestContext, sourceConfiguration.configuration.logAlias ); return hasAlias; } - public async hasMetricAlias(request: InfraFrameworkRequest, sourceId: string): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + public async hasMetricAlias( + requestContext: RequestHandlerContext, + sourceId: string + ): Promise { + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const hasAlias = await this.adapter.hasAlias( - request, + requestContext, sourceConfiguration.configuration.metricAlias ); return hasAlias; } - public async hasLogIndices(request: InfraFrameworkRequest, sourceId: string): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + public async hasLogIndices( + requestContext: RequestHandlerContext, + sourceId: string + ): Promise { + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const hasIndices = await this.adapter.hasIndices( - request, + requestContext, sourceConfiguration.configuration.logAlias ); return hasIndices; } public async hasMetricIndices( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string ): Promise { - const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId); + const sourceConfiguration = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const hasIndices = await this.adapter.hasIndices( - request, + requestContext, sourceConfiguration.configuration.metricAlias ); return hasIndices; @@ -73,7 +100,7 @@ export class InfraSourceStatus { } export interface InfraSourceStatusAdapter { - getIndexNames(request: InfraFrameworkRequest, aliasName: string): Promise; - hasAlias(request: InfraFrameworkRequest, aliasName: string): Promise; - hasIndices(request: InfraFrameworkRequest, indexNames: string): Promise; + getIndexNames(requestContext: RequestHandlerContext, aliasName: string): Promise; + hasAlias(requestContext: RequestHandlerContext, aliasName: string): Promise; + hasIndices(requestContext: RequestHandlerContext, indexNames: string): Promise; } diff --git a/x-pack/legacy/plugins/infra/server/lib/sources/sources.test.ts b/x-pack/legacy/plugins/infra/server/lib/sources/sources.test.ts index 2374a83a642df..4a83ca730ff83 100644 --- a/x-pack/legacy/plugins/infra/server/lib/sources/sources.test.ts +++ b/x-pack/legacy/plugins/infra/server/lib/sources/sources.test.ts @@ -3,34 +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 { InfraInmemoryConfigurationAdapter } from '../adapters/configuration/inmemory_configuration_adapter'; import { InfraSources } from './sources'; describe('the InfraSources lib', () => { describe('getSourceConfiguration method', () => { test('returns a source configuration if it exists', async () => { const sourcesLib = new InfraSources({ - configuration: createMockStaticConfiguration({}), - savedObjects: createMockSavedObjectsService({ - id: 'TEST_ID', - version: 'foo', - updated_at: '2000-01-01T00:00:00.000Z', - attributes: { - metricAlias: 'METRIC_ALIAS', - logAlias: 'LOG_ALIAS', - fields: { - container: 'CONTAINER', - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, - }, - }), + config: createMockStaticConfiguration({}), }); - const request: any = Symbol(); + const request: any = createRequestContext({ + id: 'TEST_ID', + version: 'foo', + updated_at: '2000-01-01T00:00:00.000Z', + attributes: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'CONTAINER', + host: 'HOST', + pod: 'POD', + tiebreaker: 'TIEBREAKER', + timestamp: 'TIMESTAMP', + }, + }, + }); expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ id: 'TEST_ID', @@ -52,7 +49,7 @@ describe('the InfraSources lib', () => { test('adds missing attributes from the static configuration to a source configuration', async () => { const sourcesLib = new InfraSources({ - configuration: createMockStaticConfiguration({ + config: createMockStaticConfiguration({ default: { metricAlias: 'METRIC_ALIAS', logAlias: 'LOG_ALIAS', @@ -64,19 +61,18 @@ describe('the InfraSources lib', () => { }, }, }), - savedObjects: createMockSavedObjectsService({ - id: 'TEST_ID', - version: 'foo', - updated_at: '2000-01-01T00:00:00.000Z', - attributes: { - fields: { - container: 'CONTAINER', - }, - }, - }), }); - const request: any = Symbol(); + const request: any = createRequestContext({ + id: 'TEST_ID', + version: 'foo', + updated_at: '2000-01-01T00:00:00.000Z', + attributes: { + fields: { + container: 'CONTAINER', + }, + }, + }); expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ id: 'TEST_ID', @@ -98,16 +94,15 @@ describe('the InfraSources lib', () => { test('adds missing attributes from the default configuration to a source configuration', async () => { const sourcesLib = new InfraSources({ - configuration: createMockStaticConfiguration({}), - savedObjects: createMockSavedObjectsService({ - id: 'TEST_ID', - version: 'foo', - updated_at: '2000-01-01T00:00:00.000Z', - attributes: {}, - }), + config: createMockStaticConfiguration({}), }); - const request: any = Symbol(); + const request: any = createRequestContext({ + id: 'TEST_ID', + version: 'foo', + updated_at: '2000-01-01T00:00:00.000Z', + attributes: {}, + }); expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ id: 'TEST_ID', @@ -129,29 +124,30 @@ describe('the InfraSources lib', () => { }); }); -const createMockStaticConfiguration = (sources: any) => - new InfraInmemoryConfigurationAdapter({ - enabled: true, - query: { - partitionSize: 1, - partitionFactor: 1, - }, - sources, - }); - -const createMockSavedObjectsService = (savedObject?: any) => ({ - getScopedSavedObjectsClient() { - return { - async get() { - return savedObject; - }, - } as any; +const createMockStaticConfiguration = (sources: any) => ({ + enabled: true, + query: { + partitionSize: 1, + partitionFactor: 1, }, - SavedObjectsClient: { - errors: { - isNotFoundError() { - return typeof savedObject === 'undefined'; + sources, +}); + +const createRequestContext = (savedObject?: any) => { + return { + core: { + savedObjects: { + client: { + async get() { + return savedObject; + }, + errors: { + isNotFoundError() { + return typeof savedObject === 'undefined'; + }, + }, + }, }, }, - }, -}); + }; +}; diff --git a/x-pack/legacy/plugins/infra/server/lib/sources/sources.ts b/x-pack/legacy/plugins/infra/server/lib/sources/sources.ts index 951556a0fe642..2b38d81e4a8d5 100644 --- a/x-pack/legacy/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/legacy/plugins/infra/server/lib/sources/sources.ts @@ -6,14 +6,10 @@ import * as runtimeTypes from 'io-ts'; import { failure } from 'io-ts/lib/PathReporter'; -import { Legacy } from 'kibana'; - import { identity, constant } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; -import { Pick3 } from '../../../common/utility_types'; -import { InfraConfigurationAdapter } from '../adapters/configuration'; -import { InfraFrameworkRequest, internalInfraFrameworkRequest } from '../adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; import { defaultSourceConfiguration } from './defaults'; import { NotFoundError } from './errors'; import { infraSourceConfigurationSavedObjectType } from './saved_object_mappings'; @@ -25,19 +21,21 @@ import { SourceConfigurationSavedObjectRuntimeType, StaticSourceConfigurationRuntimeType, } from './types'; +import { InfraConfig } from '../../../../../../plugins/infra/server'; + +interface Libs { + config: InfraConfig; +} export class InfraSources { private internalSourceConfigurations: Map = new Map(); + private readonly libs: Libs; - constructor( - private readonly libs: { - configuration: InfraConfigurationAdapter; - savedObjects: Pick & - Pick3; - } - ) {} + constructor(libs: Libs) { + this.libs = libs; + } - public async getSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) { + public async getSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) { const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration(); const savedSourceConfiguration = await this.getInternalSourceConfiguration(sourceId) @@ -53,7 +51,7 @@ export class InfraSources { })) .catch(err => err instanceof NotFoundError - ? this.getSavedSourceConfiguration(request, sourceId).then(result => ({ + ? this.getSavedSourceConfiguration(requestContext, sourceId).then(result => ({ ...result, configuration: mergeSourceConfiguration( staticDefaultSourceConfiguration, @@ -63,7 +61,7 @@ export class InfraSources { : Promise.reject(err) ) .catch(err => - this.libs.savedObjects.SavedObjectsClient.errors.isNotFoundError(err) + requestContext.core.savedObjects.client.errors.isNotFoundError(err) ? Promise.resolve({ id: sourceId, version: undefined, @@ -77,10 +75,10 @@ export class InfraSources { return savedSourceConfiguration; } - public async getAllSourceConfigurations(request: InfraFrameworkRequest) { + public async getAllSourceConfigurations(requestContext: RequestHandlerContext) { const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration(); - const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(request); + const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(requestContext); return savedSourceConfigurations.map(savedSourceConfiguration => ({ ...savedSourceConfiguration, @@ -92,7 +90,7 @@ export class InfraSources { } public async createSourceConfiguration( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, source: InfraSavedSourceConfiguration ) { @@ -104,13 +102,11 @@ export class InfraSources { ); const createdSourceConfiguration = convertSavedObjectToSavedSourceConfiguration( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalInfraFrameworkRequest]) - .create( - infraSourceConfigurationSavedObjectType, - pickSavedSourceConfiguration(newSourceConfiguration) as any, - { id: sourceId } - ) + await requestContext.core.savedObjects.client.create( + infraSourceConfigurationSavedObjectType, + pickSavedSourceConfiguration(newSourceConfiguration) as any, + { id: sourceId } + ) ); return { @@ -122,20 +118,21 @@ export class InfraSources { }; } - public async deleteSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) { - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalInfraFrameworkRequest]) - .delete(infraSourceConfigurationSavedObjectType, sourceId); + public async deleteSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) { + await requestContext.core.savedObjects.client.delete( + infraSourceConfigurationSavedObjectType, + sourceId + ); } public async updateSourceConfiguration( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, sourceId: string, sourceProperties: InfraSavedSourceConfiguration ) { const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration(); - const { configuration, version } = await this.getSourceConfiguration(request, sourceId); + const { configuration, version } = await this.getSourceConfiguration(requestContext, sourceId); const updatedSourceConfigurationAttributes = mergeSourceConfiguration( configuration, @@ -143,16 +140,14 @@ export class InfraSources { ); const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalInfraFrameworkRequest]) - .update( - infraSourceConfigurationSavedObjectType, - sourceId, - pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any, - { - version, - } - ) + await requestContext.core.savedObjects.client.update( + infraSourceConfigurationSavedObjectType, + sourceId, + pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any, + { + version, + } + ) ); return { @@ -184,7 +179,6 @@ export class InfraSources { } private async getStaticDefaultSourceConfiguration() { - const staticConfiguration = await this.libs.configuration.get(); const staticSourceConfiguration = pipe( runtimeTypes .type({ @@ -192,7 +186,7 @@ export class InfraSources { default: StaticSourceConfigurationRuntimeType, }), }) - .decode(staticConfiguration), + .decode(this.libs.config), map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration), fold(constant({}), identity) ); @@ -200,12 +194,11 @@ export class InfraSources { return mergeSourceConfiguration(defaultSourceConfiguration, staticSourceConfiguration); } - private async getSavedSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalInfraFrameworkRequest] - ); - - const savedObject = await savedObjectsClient.get( + private async getSavedSourceConfiguration( + requestContext: RequestHandlerContext, + sourceId: string + ) { + const savedObject = await requestContext.core.savedObjects.client.get( infraSourceConfigurationSavedObjectType, sourceId ); @@ -213,12 +206,8 @@ export class InfraSources { return convertSavedObjectToSavedSourceConfiguration(savedObject); } - private async getAllSavedSourceConfigurations(request: InfraFrameworkRequest) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalInfraFrameworkRequest] - ); - - const savedObjects = await savedObjectsClient.find({ + private async getAllSavedSourceConfigurations(requestContext: RequestHandlerContext) { + const savedObjects = await requestContext.core.savedObjects.client.find({ type: infraSourceConfigurationSavedObjectType, }); diff --git a/x-pack/legacy/plugins/infra/server/new_platform_index.ts b/x-pack/legacy/plugins/infra/server/new_platform_index.ts new file mode 100644 index 0000000000000..6b759ecfe9fde --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/new_platform_index.ts @@ -0,0 +1,16 @@ +/* + * 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/server'; +import { InfraServerPlugin } from './new_platform_plugin'; +import { config, InfraConfig } from '../../../../plugins/infra/server'; +import { InfraServerPluginDeps } from './lib/adapters/framework'; + +export { config, InfraConfig, InfraServerPluginDeps }; + +export function plugin(context: PluginInitializerContext) { + return new InfraServerPlugin(context); +} diff --git a/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts new file mode 100644 index 0000000000000..462a07574b2dd --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts @@ -0,0 +1,107 @@ +/* + * 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, PluginInitializerContext } from 'src/core/server'; +import { Server } from 'hapi'; +import { InfraConfig } from '../../../../plugins/infra/server'; +import { initInfraServer } from './infra_server'; +import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types'; +import { FrameworkFieldsAdapter } from './lib/adapters/fields/framework_fields_adapter'; +import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; +import { InfraKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter'; +import { KibanaMetricsAdapter } from './lib/adapters/metrics/kibana_metrics_adapter'; +import { InfraElasticsearchSourceStatusAdapter } from './lib/adapters/source_status'; +import { InfraFieldsDomain } from './lib/domains/fields_domain'; +import { InfraLogEntriesDomain } from './lib/domains/log_entries_domain'; +import { InfraMetricsDomain } from './lib/domains/metrics_domain'; +import { InfraLogAnalysis } from './lib/log_analysis'; +import { InfraSnapshot } from './lib/snapshot'; +import { InfraSourceStatus } from './lib/source_status'; +import { InfraSources } from './lib/sources'; +import { InfraServerPluginDeps } from './lib/adapters/framework'; +import { METRICS_FEATURE, LOGS_FEATURE } from './features'; +import { UsageCollector } from './usage/usage_collector'; + +export interface KbnServer extends Server { + usage: any; +} + +const DEFAULT_CONFIG: InfraConfig = { + enabled: true, + query: { + partitionSize: 75, + partitionFactor: 1.2, + }, +}; + +export class InfraServerPlugin { + public config: InfraConfig = DEFAULT_CONFIG; + public libs: InfraBackendLibs | undefined; + + constructor(context: PluginInitializerContext) { + const config$ = context.config.create(); + config$.subscribe(configValue => { + this.config = { + ...DEFAULT_CONFIG, + enabled: configValue.enabled, + query: { + ...DEFAULT_CONFIG.query, + ...configValue.query, + }, + }; + }); + } + + getLibs() { + if (!this.libs) { + throw new Error('libs not set up yet'); + } + return this.libs; + } + + setup(core: CoreSetup, plugins: InfraServerPluginDeps) { + const framework = new KibanaFramework(core, this.config, plugins); + const sources = new InfraSources({ + config: this.config, + }); + const sourceStatus = new InfraSourceStatus( + new InfraElasticsearchSourceStatusAdapter(framework), + { + sources, + } + ); + const snapshot = new InfraSnapshot({ sources, framework }); + const logAnalysis = new InfraLogAnalysis({ framework }); + + // TODO: separate these out individually and do away with "domains" as a temporary group + const domainLibs: InfraDomainLibs = { + fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { + sources, + }), + logEntries: new InfraLogEntriesDomain(new InfraKibanaLogEntriesAdapter(framework), { + sources, + }), + metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)), + }; + + this.libs = { + configuration: this.config, + framework, + logAnalysis, + snapshot, + sources, + sourceStatus, + ...domainLibs, + }; + + plugins.features.registerFeature(METRICS_FEATURE); + plugins.features.registerFeature(LOGS_FEATURE); + + initInfraServer(this.libs); + + // Telemetry + UsageCollector.registerUsageCollector(plugins.usageCollection); + } +} diff --git a/x-pack/legacy/plugins/infra/server/routes/ip_to_hostname.ts b/x-pack/legacy/plugins/infra/server/routes/ip_to_hostname.ts index 16837298f0704..5ad79b3d17a13 100644 --- a/x-pack/legacy/plugins/infra/server/routes/ip_to_hostname.ts +++ b/x-pack/legacy/plugins/infra/server/routes/ip_to_hostname.ts @@ -3,18 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; -import { boomify, notFound } from 'boom'; import { first } from 'lodash'; +import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../lib/infra_types'; -import { InfraWrappableRequest } from '../lib/adapters/framework'; - -interface IpToHostRequest { - ip: string; - index_pattern: string; -} - -type IpToHostWrappedRequest = InfraWrappableRequest; export interface IpToHostResponse { host: string; @@ -28,40 +19,47 @@ interface HostDoc { }; } -const ipToHostSchema = Joi.object({ - ip: Joi.string().required(), - index_pattern: Joi.string().required(), +const ipToHostSchema = schema.object({ + ip: schema.string(), + index_pattern: schema.string(), }); export const initIpToHostName = ({ framework }: InfraBackendLibs) => { const { callWithRequest } = framework; - framework.registerRoute>({ - method: 'POST', - path: '/api/infra/ip_to_host', - options: { - validate: { payload: ipToHostSchema }, + framework.registerRoute( + { + method: 'post', + path: '/api/infra/ip_to_host', + validate: { + body: ipToHostSchema, + }, }, - handler: async req => { + async (requestContext, { body }, response) => { try { const params = { - index: req.payload.index_pattern, + index: body.index_pattern, body: { size: 1, query: { - match: { 'host.ip': req.payload.ip }, + match: { 'host.ip': body.ip }, }, _source: ['host.name'], }, }; - const response = await callWithRequest(req, 'search', params); - if (response.hits.total.value === 0) { - throw notFound('Host with matching IP address not found.'); + const { hits } = await callWithRequest(requestContext, 'search', params); + if (hits.total.value === 0) { + return response.notFound({ + body: { message: 'Host with matching IP address not found.' }, + }); } - const hostDoc = first(response.hits.hits); - return { host: hostDoc._source.host.name }; - } catch (e) { - throw boomify(e); + const hostDoc = first(hits.hits); + return response.ok({ body: { host: hostDoc._source.host.name } }); + } catch ({ statusCode = 500, message = 'Unknown error occurred' }) { + return response.customError({ + statusCode, + body: { message }, + }); } - }, - }); + } + ); }; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts index 0a369adb7ca29..1f64da1859b5f 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts @@ -8,7 +8,7 @@ import Boom from 'boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; - +import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../../../lib/infra_types'; import { LOG_ANALYSIS_VALIDATION_INDICES_PATH, @@ -20,64 +20,75 @@ import { import { throwErrors } from '../../../../common/runtime_types'; const partitionField = 'event.dataset'; +const escapeHatch = schema.object({}, { allowUnknowns: true }); export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) => { - framework.registerRoute({ - method: 'POST', - path: LOG_ANALYSIS_VALIDATION_INDICES_PATH, - handler: async (req, res) => { - const payload = pipe( - validationIndicesRequestPayloadRT.decode(req.payload), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { timestampField, indices } = payload.data; - const errors: ValidationIndicesError[] = []; + framework.registerRoute( + { + method: 'post', + path: LOG_ANALYSIS_VALIDATION_INDICES_PATH, + validate: { body: escapeHatch }, + }, + async (requestContext, request, response) => { + try { + const payload = pipe( + validationIndicesRequestPayloadRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - // Query each pattern individually, to map correctly the errors - await Promise.all( - indices.map(async index => { - const fieldCaps = await framework.callWithRequest(req, 'fieldCaps', { - index, - fields: `${timestampField},${partitionField}`, - }); + const { timestampField, indices } = payload.data; + const errors: ValidationIndicesError[] = []; - if (fieldCaps.indices.length === 0) { - errors.push({ - error: 'INDEX_NOT_FOUND', + // Query each pattern individually, to map correctly the errors + await Promise.all( + indices.map(async index => { + const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { index, + fields: `${timestampField},${partitionField}`, }); - return; - } - ([ - [timestampField, 'date'], - [partitionField, 'keyword'], - ] as const).forEach(([field, fieldType]) => { - const fieldMetadata = fieldCaps.fields[field]; - - if (fieldMetadata === undefined) { + if (fieldCaps.indices.length === 0) { errors.push({ - error: 'FIELD_NOT_FOUND', + error: 'INDEX_NOT_FOUND', index, - field, }); - } else { - const fieldTypes = Object.keys(fieldMetadata); + return; + } + + ([ + [timestampField, 'date'], + [partitionField, 'keyword'], + ] as const).forEach(([field, fieldType]) => { + const fieldMetadata = fieldCaps.fields[field]; - if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) { + if (fieldMetadata === undefined) { errors.push({ - error: `FIELD_NOT_VALID`, + error: 'FIELD_NOT_FOUND', index, field, }); - } - } - }); - }) - ); + } else { + const fieldTypes = Object.keys(fieldMetadata); - return res.response(validationIndicesResponsePayloadRT.encode({ data: { errors } })); - }, - }); + if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) { + errors.push({ + error: `FIELD_NOT_VALID`, + index, + field, + }); + } + } + }); + }) + ); + return response.ok({ + body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); }; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts index fc06ea48f4353..973080c880e6d 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts @@ -9,6 +9,7 @@ import Boom from 'boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../../../lib/infra_types'; import { LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, @@ -19,46 +20,58 @@ import { import { throwErrors } from '../../../../common/runtime_types'; import { NoLogRateResultsIndexError } from '../../../lib/log_analysis'; +const anyObject = schema.object({}, { allowUnknowns: true }); + export const initLogAnalysisGetLogEntryRateRoute = ({ framework, logAnalysis, }: InfraBackendLibs) => { - framework.registerRoute({ - method: 'POST', - path: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, - handler: async (req, res) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, + validate: { + // short-circuit forced @kbn/config-schema validation so we can do io-ts validation + body: anyObject, + }, + }, + async (requestContext, request, response) => { const payload = pipe( - getLogEntryRateRequestPayloadRT.decode(req.payload), + getLogEntryRateRequestPayloadRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const logEntryRateBuckets = await logAnalysis - .getLogEntryRateBuckets( - req, + try { + const logEntryRateBuckets = await logAnalysis.getLogEntryRateBuckets( + requestContext, payload.data.sourceId, payload.data.timeRange.startTime, payload.data.timeRange.endTime, - payload.data.bucketDuration - ) - .catch(err => { - if (err instanceof NoLogRateResultsIndexError) { - throw Boom.boomify(err, { statusCode: 404 }); - } + payload.data.bucketDuration, + request + ); - throw Boom.boomify(err, { statusCode: ('statusCode' in err && err.statusCode) || 500 }); + return response.ok({ + body: getLogEntryRateSuccessReponsePayloadRT.encode({ + data: { + bucketDuration: payload.data.bucketDuration, + histogramBuckets: logEntryRateBuckets, + totalNumberOfLogEntries: getTotalNumberOfLogEntries(logEntryRateBuckets), + }, + }), }); - - return res.response( - getLogEntryRateSuccessReponsePayloadRT.encode({ - data: { - bucketDuration: payload.data.bucketDuration, - histogramBuckets: logEntryRateBuckets, - totalNumberOfLogEntries: getTotalNumberOfLogEntries(logEntryRateBuckets), - }, - }) - ); - }, - }); + } catch (e) { + const { statusCode = 500, message = 'Unknown error occurred' } = e; + if (e instanceof NoLogRateResultsIndexError) { + return response.notFound({ body: { message } }); + } + return response.customError({ + statusCode, + body: { message }, + }); + } + } + ); }; const getTotalNumberOfLogEntries = ( diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/index.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/index.ts index 8cdb121aebf1e..a1f6311a103eb 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/index.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom, { boomify } from 'boom'; +import { schema } from '@kbn/config-schema'; +import Boom from 'boom'; import { get } from 'lodash'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { - InfraMetadata, - InfraMetadataWrappedRequest, InfraMetadataFeature, InfraMetadataRequestRT, InfraMetadataRT, @@ -24,23 +23,33 @@ import { getCloudMetricsMetadata } from './lib/get_cloud_metric_metadata'; import { getNodeInfo } from './lib/get_node_info'; import { throwErrors } from '../../../common/runtime_types'; +const escapeHatch = schema.object({}, { allowUnknowns: true }); + export const initMetadataRoute = (libs: InfraBackendLibs) => { const { framework } = libs; - framework.registerRoute>({ - method: 'POST', - path: '/api/infra/metadata', - handler: async req => { + framework.registerRoute( + { + method: 'post', + path: '/api/infra/metadata', + validate: { + body: escapeHatch, + }, + }, + async (requestContext, request, response) => { try { const { nodeId, nodeType, sourceId } = pipe( - InfraMetadataRequestRT.decode(req.payload), + InfraMetadataRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const { configuration } = await libs.sources.getSourceConfiguration(req, sourceId); + const { configuration } = await libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); const metricsMetadata = await getMetricMetadata( framework, - req, + requestContext, configuration, nodeId, nodeType @@ -49,35 +58,35 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { nameToFeature('metrics') ); - const info = await getNodeInfo(framework, req, configuration, nodeId, nodeType); + const info = await getNodeInfo(framework, requestContext, configuration, nodeId, nodeType); const cloudInstanceId = get(info, 'cloud.instance.id'); const cloudMetricsMetadata = cloudInstanceId - ? await getCloudMetricsMetadata(framework, req, configuration, cloudInstanceId) + ? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId) : { buckets: [] }; const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( nameToFeature('metrics') ); - - const hasAPM = await hasAPMData(framework, req, configuration, nodeId, nodeType); + const hasAPM = await hasAPMData(framework, requestContext, configuration, nodeId, nodeType); const apmMetricFeatures = hasAPM ? [{ name: 'apm.transaction', source: 'apm' }] : []; const id = metricsMetadata.id; const name = metricsMetadata.name || id; - return pipe( - InfraMetadataRT.decode({ + return response.ok({ + body: InfraMetadataRT.encode({ id, name, features: [...metricFeatures, ...cloudMetricsFeatures, ...apmMetricFeatures], info, }), - fold(throwErrors(Boom.badImplementation), identity) - ); + }); } catch (error) { - throw boomify(error); + return response.internalError({ + body: error.message, + }); } - }, - }); + } + ); }; const nameToFeature = (source: string) => (name: string): InfraMetadataFeature => ({ diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts index 58b3beab42886..75ca3ae3caee2 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RequestHandlerContext } from 'src/core/server'; import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, InfraMetadataAggregationBucket, InfraMetadataAggregationResponse, } from '../../../lib/adapters/framework'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { CLOUD_METRICS_MODULES } from '../../../lib/constants'; @@ -18,8 +18,8 @@ export interface InfraCloudMetricsAdapterResponse { } export const getCloudMetricsMetadata = async ( - framework: InfraBackendFrameworkAdapter, - req: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, instanceId: string ): Promise => { @@ -51,7 +51,7 @@ export const getCloudMetricsMetadata = async ( { metrics?: InfraMetadataAggregationResponse; } - >(req, 'search', metricQuery); + >(requestContext, 'search', metricQuery); const buckets = response.aggregations && response.aggregations.metrics diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts index 812bc27fffc8a..3bd22062c26a0 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -5,12 +5,12 @@ */ import { get } from 'lodash'; +import { RequestHandlerContext } from 'src/core/server'; import { - InfraFrameworkRequest, InfraMetadataAggregationBucket, - InfraBackendFrameworkAdapter, InfraMetadataAggregationResponse, } from '../../../lib/adapters/framework'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { getIdFieldName } from './get_id_field_name'; import { NAME_FIELDS } from '../../../lib/constants'; @@ -22,8 +22,8 @@ export interface InfraMetricsAdapterResponse { } export const getMetricMetadata = async ( - framework: InfraBackendFrameworkAdapter, - req: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: 'host' | 'pod' | 'container' @@ -69,7 +69,7 @@ export const getMetricMetadata = async ( metrics?: InfraMetadataAggregationResponse; nodeName?: InfraMetadataAggregationResponse; } - >(req, 'search', metricQuery); + >(requestContext, 'search', metricQuery); const buckets = response.aggregations && response.aggregations.metrics diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts index 5af25515a42ed..1567b6d1bd1ec 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts @@ -5,10 +5,8 @@ */ import { first } from 'lodash'; -import { - InfraFrameworkRequest, - InfraBackendFrameworkAdapter, -} from '../../../lib/adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { InfraNodeType } from '../../../graphql/types'; import { InfraMetadataInfo } from '../../../../common/http_api/metadata_api'; @@ -17,8 +15,8 @@ import { CLOUD_METRICS_MODULES } from '../../../lib/constants'; import { getIdFieldName } from './get_id_field_name'; export const getNodeInfo = async ( - framework: InfraBackendFrameworkAdapter, - req: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: 'host' | 'pod' | 'container' @@ -31,7 +29,7 @@ export const getNodeInfo = async ( if (nodeType === InfraNodeType.pod) { const kubernetesNodeName = await getPodNodeName( framework, - req, + requestContext, sourceConfiguration, nodeId, nodeType @@ -39,7 +37,7 @@ export const getNodeInfo = async ( if (kubernetesNodeName) { return getNodeInfo( framework, - req, + requestContext, sourceConfiguration, kubernetesNodeName, InfraNodeType.host @@ -64,7 +62,7 @@ export const getNodeInfo = async ( }, }; const response = await framework.callWithRequest<{ _source: InfraMetadataInfo }, {}>( - req, + requestContext, 'search', params ); diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts index 893707a4660ee..47ffc7f83b6bc 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_pod_node_name.ts @@ -5,16 +5,14 @@ */ import { first, get } from 'lodash'; -import { - InfraFrameworkRequest, - InfraBackendFrameworkAdapter, -} from '../../../lib/adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { getIdFieldName } from './get_id_field_name'; export const getPodNodeName = async ( - framework: InfraBackendFrameworkAdapter, - req: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: 'host' | 'pod' | 'container' @@ -40,7 +38,7 @@ export const getPodNodeName = async ( const response = await framework.callWithRequest< { _source: { kubernetes: { node: { name: string } } } }, {} - >(req, 'search', params); + >(requestContext, 'search', params); const firstHit = first(response.hits.hits); if (firstHit) { return get(firstHit, '_source.kubernetes.node.name'); diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts index 3193cf83978b0..ab242804173c0 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts @@ -4,22 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - InfraFrameworkRequest, - InfraBackendFrameworkAdapter, -} from '../../../lib/adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; + +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { getIdFieldName } from './get_id_field_name'; export const hasAPMData = async ( - framework: InfraBackendFrameworkAdapter, - req: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: 'host' | 'pod' | 'container' ) => { - const config = framework.config(req); - const apmIndex = config.get('apm_oss.transactionIndices') || 'apm-*'; + const apmIndices = await framework.plugins.apm.getApmIndices( + requestContext.core.savedObjects.client + ); + const apmIndex = apmIndices['apm_oss.transactionIndices'] || 'apm-*'; + // There is a bug in APM ECS data where host.name is not set. // This will fixed with: https://github.com/elastic/apm-server/issues/2502 const nodeFieldName = @@ -48,6 +50,6 @@ export const hasAPMData = async ( }, }, }; - const response = await framework.callWithRequest<{}, {}>(req, 'search', params); + const response = await framework.callWithRequest<{}, {}>(requestContext, 'search', params); return response.hits.total.value !== 0; }; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/index.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/index.ts index 6b724f6ac60fd..0c69034c66940 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/index.ts @@ -4,42 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../../lib/infra_types'; import { getGroupings } from './lib/get_groupings'; import { populateSeriesWithTSVBData } from './lib/populate_series_with_tsvb_data'; -import { metricsExplorerSchema } from './schema'; -import { MetricsExplorerResponse, MetricsExplorerWrappedRequest } from './types'; +import { MetricsExplorerRequestBody } from './types'; +// import { metricsExplorerSchema } from './schema'; +// import { MetricsExplorerResponse, MetricsExplorerRequestBody } from './types'; + +// NP_TODO: need to replace all of this with real types or io-ts or something? +const escapeHatch = schema.object({}, { allowUnknowns: true }); export const initMetricExplorerRoute = (libs: InfraBackendLibs) => { const { framework } = libs; const { callWithRequest } = framework; - framework.registerRoute>({ - method: 'POST', - path: '/api/infra/metrics_explorer', - options: { + framework.registerRoute( + { + method: 'post', + path: '/api/infra/metrics_explorer', validate: { - payload: metricsExplorerSchema, + body: escapeHatch, }, }, - handler: async req => { + async (requestContext, request, response) => { try { const search = (searchOptions: object) => - callWithRequest<{}, Aggregation>(req, 'search', searchOptions); - const options = req.payload; + callWithRequest<{}, Aggregation>(requestContext, 'search', searchOptions); + const options = request.body as MetricsExplorerRequestBody; // Need to remove this casting and swap in config-schema demands :( // First we get the groupings from a composite aggregation - const response = await getGroupings(search, options); + const groupings = await getGroupings(search, options); // Then we take the results and fill in the data from TSVB with the // user's custom metrics const seriesWithMetrics = await Promise.all( - response.series.map(populateSeriesWithTSVBData(req, options, framework)) + groupings.series.map( + populateSeriesWithTSVBData(request, options, framework, requestContext) + ) ); - return { ...response, series: seriesWithMetrics }; + return response.ok({ body: { ...groupings, series: seriesWithMetrics } }); } catch (error) { - throw boomify(error); + return response.internalError({ + body: error.message, + }); } - }, - }); + } + ); }; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts index 6b7f85f7e5952..64b9fba0e7aa2 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts @@ -5,10 +5,10 @@ */ import { InfraMetricModelMetricType } from '../../../lib/adapters/metrics'; -import { MetricsExplorerAggregation, MetricsExplorerRequest } from '../types'; +import { MetricsExplorerAggregation, MetricsExplorerRequestBody } from '../types'; import { InfraMetric } from '../../../graphql/types'; import { TSVBMetricModel } from '../../../../common/inventory_models/types'; -export const createMetricModel = (options: MetricsExplorerRequest): TSVBMetricModel => { +export const createMetricModel = (options: MetricsExplorerRequestBody): TSVBMetricModel => { return { id: InfraMetric.custom, requires: [], diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_groupings.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_groupings.ts index 994de72f8029a..7111d3e7f8ca4 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_groupings.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_groupings.ts @@ -6,7 +6,7 @@ import { isObject, set } from 'lodash'; import { InfraDatabaseSearchResponse } from '../../../lib/adapters/framework'; -import { MetricsExplorerRequest, MetricsExplorerResponse } from '../types'; +import { MetricsExplorerRequestBody, MetricsExplorerResponse } from '../types'; interface GroupingAggregation { groupingsCount: { @@ -27,7 +27,7 @@ const EMPTY_RESPONSE = { export const getGroupings = async ( search: (options: object) => Promise>, - options: MetricsExplorerRequest + options: MetricsExplorerRequestBody ): Promise => { if (!options.groupBy) { return EMPTY_RESPONSE; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts index 80ccad9567a0f..1a0edb1053730 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts @@ -5,25 +5,23 @@ */ import { union } from 'lodash'; -import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, -} from '../../../lib/adapters/framework'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { MetricsExplorerColumnType, - MetricsExplorerRequest, MetricsExplorerRow, MetricsExplorerSeries, - MetricsExplorerWrappedRequest, + MetricsExplorerRequestBody, } from '../types'; import { createMetricModel } from './create_metrics_model'; import { JsonObject } from '../../../../common/typed_json'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; export const populateSeriesWithTSVBData = ( - req: InfraFrameworkRequest, - options: MetricsExplorerRequest, - framework: InfraBackendFrameworkAdapter + request: KibanaRequest, + options: MetricsExplorerRequestBody, + framework: KibanaFramework, + requestContext: RequestHandlerContext ) => async (series: MetricsExplorerSeries) => { // IF there are no metrics selected then we should return an empty result. if (options.metrics.length === 0) { @@ -57,7 +55,7 @@ export const populateSeriesWithTSVBData = ( const model = createMetricModel(options); const calculatedInterval = await calculateMetricInterval( framework, - req, + requestContext, { indexPattern: options.indexPattern, timestampField: options.timerange.field, @@ -78,7 +76,13 @@ export const populateSeriesWithTSVBData = ( } // Get TSVB results using the model, timerange and filters - const tsvbResults = await framework.makeTSVBRequest(req, model, timerange, filters); + const tsvbResults = await framework.makeTSVBRequest( + request, + model, + timerange, + filters, + requestContext + ); // If there is no data `custom` will not exist. if (!tsvbResults.custom) { diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/types.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/types.ts index b29c41fcbff18..a43e3adbdd184 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/types.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/types.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraWrappableRequest } from '../../lib/adapters/framework'; - export interface InfraTimerange { field: string; from: number; @@ -27,7 +25,7 @@ export interface MetricsExplorerMetric { field?: string | undefined; } -export interface MetricsExplorerRequest { +export interface MetricsExplorerRequestBody { timerange: InfraTimerange; indexPattern: string; metrics: MetricsExplorerMetric[]; @@ -37,8 +35,6 @@ export interface MetricsExplorerRequest { filterQuery?: string; } -export type MetricsExplorerWrappedRequest = InfraWrappableRequest; - export interface MetricsExplorerPageInfo { total: number; afterKey?: string | null; diff --git a/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts b/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts index a4bc84433a4c1..a9419cd27e684 100644 --- a/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from 'boom'; -import { boomify } from 'boom'; +import { schema } from '@kbn/config-schema'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; @@ -13,27 +13,34 @@ import { UsageCollector } from '../../usage/usage_collector'; import { InfraMetricsRequestOptions } from '../../lib/adapters/metrics'; import { InfraNodeType, InfraMetric } from '../../graphql/types'; import { - NodeDetailsWrappedRequest, NodeDetailsRequestRT, - NodeDetailsMetricDataResponse, + NodeDetailsMetricDataResponseRT, } from '../../../common/http_api/node_details_api'; import { throwErrors } from '../../../common/runtime_types'; +const escapeHatch = schema.object({}, { allowUnknowns: true }); + export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { const { framework } = libs; - framework.registerRoute>({ - method: 'POST', - path: '/api/metrics/node_details', - handler: async req => { - const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( - NodeDetailsRequestRT.decode(req.payload), - fold(throwErrors(Boom.badRequest), identity) - ); + framework.registerRoute( + { + method: 'post', + path: '/api/metrics/node_details', + validate: { + body: escapeHatch, + }, + }, + async (requestContext, request, response) => { try { - const source = await libs.sources.getSourceConfiguration(req, sourceId); + const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( + NodeDetailsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); UsageCollector.countNode(nodeType); + const options: InfraMetricsRequestOptions = { nodeIds: { nodeId, @@ -44,13 +51,16 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { metrics: metrics as InfraMetric[], timerange, }; - - return { - metrics: await libs.metrics.getMetrics(req, options), - }; - } catch (e) { - throw boomify(e); + return response.ok({ + body: NodeDetailsMetricDataResponseRT.encode({ + metrics: await libs.metrics.getMetrics(requestContext, options, request), + }), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); } - }, - }); + } + ); }; diff --git a/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts b/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts index 61d2fccf00101..013a261d24831 100644 --- a/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from 'boom'; +import { schema } from '@kbn/config-schema'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; @@ -12,37 +13,50 @@ import { InfraSnapshotRequestOptions } from '../../lib/snapshot'; import { UsageCollector } from '../../usage/usage_collector'; import { parseFilterQuery } from '../../utils/serialized_query'; import { InfraNodeType, InfraSnapshotMetricInput } from '../../../public/graphql/types'; -import { - SnapshotRequestRT, - SnapshotWrappedRequest, - SnapshotNodeResponse, -} from '../../../common/http_api/snapshot_api'; +import { SnapshotRequestRT, SnapshotNodeResponseRT } from '../../../common/http_api/snapshot_api'; import { throwErrors } from '../../../common/runtime_types'; +const escapeHatch = schema.object({}, { allowUnknowns: true }); + export const initSnapshotRoute = (libs: InfraBackendLibs) => { const { framework } = libs; - framework.registerRoute>({ - method: 'POST', - path: '/api/metrics/snapshot', - handler: async req => { - const { filterQuery, nodeType, groupBy, sourceId, metric, timerange } = pipe( - SnapshotRequestRT.decode(req.payload), - fold(throwErrors(Boom.badRequest), identity) - ); - const source = await libs.sources.getSourceConfiguration(req, sourceId); - UsageCollector.countNode(nodeType); - const options: InfraSnapshotRequestOptions = { - filterQuery: parseFilterQuery(filterQuery), - // TODO: Use common infra metric and replace graphql type - nodeType: nodeType as InfraNodeType, - groupBy, - sourceConfiguration: source.configuration, - // TODO: Use common infra metric and replace graphql type - metric: metric as InfraSnapshotMetricInput, - timerange, - }; - return { nodes: await libs.snapshot.getNodes(req, options) }; + framework.registerRoute( + { + method: 'post', + path: '/api/metrics/snapshot', + validate: { + body: escapeHatch, + }, }, - }); + async (requestContext, request, response) => { + try { + const { filterQuery, nodeType, groupBy, sourceId, metric, timerange } = pipe( + SnapshotRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); + UsageCollector.countNode(nodeType); + const options: InfraSnapshotRequestOptions = { + filterQuery: parseFilterQuery(filterQuery), + // TODO: Use common infra metric and replace graphql type + nodeType: nodeType as InfraNodeType, + groupBy, + sourceConfiguration: source.configuration, + // TODO: Use common infra metric and replace graphql type + metric: metric as InfraSnapshotMetricInput, + timerange, + }; + return response.ok({ + body: SnapshotNodeResponseRT.encode({ + nodes: await libs.snapshot.getNodes(requestContext, options), + }), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); }; diff --git a/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts b/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts index 7696abd2ac250..5eb5d424cdd73 100644 --- a/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts +++ b/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../lib/adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; +import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter'; interface Options { indexPattern: string; @@ -20,8 +21,8 @@ interface Options { * This is useful for visualizing metric modules like s3 that only send metrics once per day. */ export const calculateMetricInterval = async ( - framework: InfraBackendFrameworkAdapter, - request: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, options: Options, modules: string[] ) => { @@ -64,7 +65,11 @@ export const calculateMetricInterval = async ( }, }; - const resp = await framework.callWithRequest<{}, PeriodAggregationData>(request, 'search', query); + const resp = await framework.callWithRequest<{}, PeriodAggregationData>( + requestContext, + 'search', + query + ); // if ES doesn't return an aggregations key, something went seriously wrong. if (!resp.aggregations) { diff --git a/x-pack/legacy/plugins/infra/server/utils/get_all_composite_data.ts b/x-pack/legacy/plugins/infra/server/utils/get_all_composite_data.ts index a5729b6004dcf..c7ff1b077f685 100644 --- a/x-pack/legacy/plugins/infra/server/utils/get_all_composite_data.ts +++ b/x-pack/legacy/plugins/infra/server/utils/get_all_composite_data.ts @@ -4,25 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - InfraBackendFrameworkAdapter, - InfraFrameworkRequest, - InfraDatabaseSearchResponse, -} from '../lib/adapters/framework'; +import { RequestHandlerContext } from 'src/core/server'; +import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter'; +import { InfraDatabaseSearchResponse } from '../lib/adapters/framework'; export const getAllCompositeData = async < Aggregation = undefined, Bucket = {}, Options extends object = {} >( - framework: InfraBackendFrameworkAdapter, - request: InfraFrameworkRequest, + framework: KibanaFramework, + requestContext: RequestHandlerContext, options: Options, bucketSelector: (response: InfraDatabaseSearchResponse<{}, Aggregation>) => Bucket[], onAfterKey: (options: Options, response: InfraDatabaseSearchResponse<{}, Aggregation>) => Options, previousBuckets: Bucket[] = [] ): Promise => { - const response = await framework.callWithRequest<{}, Aggregation>(request, 'search', options); + const response = await framework.callWithRequest<{}, Aggregation>( + requestContext, + 'search', + options + ); // Nothing available, return the previous buckets. if (response.hits.total.value === 0) { @@ -45,7 +47,7 @@ export const getAllCompositeData = async < const newOptions = onAfterKey(options, response); return getAllCompositeData( framework, - request, + requestContext, newOptions, bucketSelector, onAfterKey, diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index ce05af46ade66..3ffc7bcf275d7 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -106,14 +106,10 @@ describe('Lens App', () => { }, }, }, - }, - dataShim: { indexPatterns: { - indexPatterns: { - get: jest.fn(id => { - return new Promise(resolve => resolve({ id })); - }), - }, + get: jest.fn(id => { + return new Promise(resolve => resolve({ id })); + }), }, }, storage: { @@ -238,7 +234,7 @@ describe('Lens App', () => { await waitForPromises(); expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.dataShim.indexPatterns.indexPatterns.get).toHaveBeenCalledWith('1'); + expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1'); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: 'fake query', diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index c29b0df9ee9fa..fda07c9b6bb77 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -11,12 +11,7 @@ import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { AppMountContext, NotificationsStart } from 'src/core/public'; -import { - DataStart, - IndexPattern as IndexPatternInstance, - IndexPatterns as IndexPatternsService, - SavedQuery, -} from 'src/legacy/core_plugins/data/public'; +import { SavedQuery } from 'src/legacy/core_plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -24,7 +19,11 @@ import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; import { NativeRenderer } from '../native_renderer'; import { trackUiEvent } from '../lens_ui_telemetry'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + IndexPattern as IndexPatternInstance, + IndexPatternsContract, +} from '../../../../../../src/plugins/data/public'; interface State { isLoading: boolean; @@ -46,7 +45,6 @@ interface State { export function App({ editorFrame, data, - dataShim, core, storage, docId, @@ -56,7 +54,6 @@ export function App({ editorFrame: EditorFrameInstance; data: DataPublicPluginStart; core: AppMountContext['core']; - dataShim: DataStart; storage: IStorageWrapper; docId?: string; docStorage: SavedObjectStore; @@ -119,7 +116,7 @@ export function App({ .then(doc => { getAllIndexPatterns( doc.state.datasourceMetaData.filterableIndexPatterns, - dataShim.indexPatterns.indexPatterns, + data.indexPatterns, core.notifications ) .then(indexPatterns => { @@ -286,7 +283,7 @@ export function App({ ) { getAllIndexPatterns( filterableIndexPatterns, - dataShim.indexPatterns.indexPatterns, + data.indexPatterns, core.notifications ).then(indexPatterns => { if (indexPatterns) { @@ -349,7 +346,7 @@ export function App({ export async function getAllIndexPatterns( ids: Array<{ id: string }>, - indexPatternsService: IndexPatternsService, + indexPatternsService: IndexPatternsContract, notifications: NotificationsStart ): Promise { try { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 6493af28f89ea..b1eac8e287bd8 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -82,7 +82,7 @@ export class AppPlugin { if (this.startDependencies === null) { throw new Error('mounted before start phase'); } - const { data, dataShim, savedObjectsClient, editorFrame } = this.startDependencies; + const { data, savedObjectsClient, editorFrame } = this.startDependencies; addHelpMenuToAppChrome(context.core.chrome); const instance = editorFrame.createInstance({}); @@ -100,7 +100,6 @@ export class AppPlugin { editorFrame.setup(npSetup.core, { - data: dataSetup, + data: npSetup.plugins.data, embeddable: npSetup.plugins.embeddable, expressions: npSetup.plugins.expressions, }); export const editorFrameStart = () => editorFrame.start(npStart.core, { - data: dataStart, + data: npStart.plugins.data, embeddable: npStart.plugins.embeddable, expressions: npStart.plugins.expressions, chrome, diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap similarity index 77% rename from x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap rename to x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9d5e91c2d5f16..5d6f10b1bda3e 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -130,6 +130,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem ] } needsAcknowledgement={true} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -844,99 +845,115 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
- +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+
- +
@@ -1164,6 +1181,7 @@ exports[`UploadLicense should display an error when ES says license is expired 1 errorMessage="The supplied license has expired." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -1291,103 +1309,119 @@ exports[`UploadLicense should display an error when ES says license is expired 1 - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ @@ -1615,6 +1649,7 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 errorMessage="The supplied license is not valid for this product." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -1742,103 +1777,119 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ @@ -2066,6 +2117,7 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] errorMessage="Error encountered uploading license: Check your license file." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -2193,99 +2245,115 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ @@ -2513,6 +2581,7 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` errorMessage="Error encountered uploading license: Can not upgrade to a production license unless TLS is configured or security is disabled" isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -2640,103 +2709,119 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ diff --git a/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js b/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js index a0f4ebdd39b0e..1d1b98fb18807 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AddLicense } from '../public/sections/license_dashboard/add_license'; +import { AddLicense } from '../public/np_ready/application/sections/license_dashboard/add_license'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js b/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js index 9a6aaf95afa6b..ff9b2e1c786b5 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LicenseStatus } from '../public/sections/license_dashboard/license_status'; +import { LicenseStatus } from '../public/np_ready/application/sections/license_dashboard/license_status'; import { createMockLicense, getComponent } from './util'; describe('LicenseStatus component', () => { diff --git a/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js b/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js index e990bd48317d1..226b3bac007b0 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestTrialExtension } from '../public/sections/license_dashboard/request_trial_extension'; +import { RequestTrialExtension } from '../public/np_ready/application/sections/license_dashboard/request_trial_extension'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js b/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js index 76057ff95d41f..3b9ca4487dfb3 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RevertToBasic } from '../public/sections/license_dashboard/revert_to_basic'; +import { RevertToBasic } from '../public/np_ready/application/sections/license_dashboard/revert_to_basic'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js b/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js index 928c7df70d0b7..903be98f225f2 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { StartTrial } from '../public/sections/license_dashboard/start_trial'; +import { StartTrial } from '../public/np_ready/application/sections/license_dashboard/start_trial'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js b/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js index a92ca384e8a37..70edc67b449bb 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { setTelemetryEnabled, setTelemetryOptInService } from '../public/lib/telemetry'; -import { TelemetryOptIn } from '../public/components/telemetry_opt_in'; +import { setTelemetryEnabled, setTelemetryOptInService } from '../public/np_ready/application/lib/telemetry'; +import { TelemetryOptIn } from '../public/np_ready/application/components/telemetry_opt_in'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; jest.mock('ui/capabilities', () => ({ diff --git a/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js b/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx similarity index 62% rename from x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js rename to x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx index 04da56b7ee82b..b852daf78ca15 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx @@ -4,43 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ +import { httpServiceMock, chromeServiceMock } from '../../../../../src/core/public/mocks'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; import React from 'react'; import { Provider } from 'react-redux'; -import { uploadLicense } from '../public/store/actions/upload_license'; -import { licenseManagementStore } from '../public/store/store'; -import { UploadLicense } from '../public/sections/upload_license'; -import { BASE_PATH } from '../common/constants'; + +// @ts-ignore +import { uploadLicense } from '../public/np_ready/application/store/actions/upload_license'; + +// @ts-ignore +import { licenseManagementStore } from '../public/np_ready/application/store/store'; + +// @ts-ignore +import { UploadLicense } from '../public/np_ready/application/sections/upload_license'; + import { UPLOAD_LICENSE_EXPIRED, UPLOAD_LICENSE_REQUIRES_ACK, UPLOAD_LICENSE_SUCCESS, UPLOAD_LICENSE_TLS_NOT_ENABLED, UPLOAD_LICENSE_INVALID, + // @ts-ignore } from './api_responses'; -import sinon from 'sinon'; window.location.reload = () => {}; -let server = null; -let store = null; -let component = null; + +let store: any = null; +let component: any = null; const services = { - kbnUrl: { - change: jest.fn() + legacy: { + xPackInfo: { + refresh: jest.fn(), + get: () => { + return { license: { type: 'basic' } }; + }, + }, + refreshXpack: jest.fn(), + }, + http: httpServiceMock.createSetupContract(), + chrome: chromeServiceMock.createStartContract(), + history: { + replace: jest.fn(), }, - autoLogout: () => {}, - xPackInfo: { - refresh: jest.fn(), - get: () => { - return { license: { type: 'basic' } }; - } - } }; describe('UploadLicense', () => { beforeEach(() => { - server = sinon.fakeServer.create(); - server.respondImmediately = true; store = licenseManagementStore({}, services); component = ( @@ -48,57 +57,60 @@ describe('UploadLicense', () => { ); }); + afterEach(() => { - server.restore(); - services.xPackInfo.refresh.mockReset(); - services.kbnUrl.change.mockReset(); + services.legacy.xPackInfo.refresh.mockReset(); + services.history.replace.mockReset(); + jest.clearAllMocks(); }); + it('should display an error when submitting invalid JSON', async () => { const rendered = mountWithIntl(component); store.dispatch(uploadLicense('INVALID', 'trial')); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display an error when ES says license is invalid', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_INVALID[2])); const rendered = mountWithIntl(component); const invalidLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_INVALID); await uploadLicense(invalidLicense)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display an error when ES says license is expired', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_EXPIRED[2])); const rendered = mountWithIntl(component); const invalidLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_EXPIRED); await uploadLicense(invalidLicense)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display a modal when license requires acknowledgement', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_REQUIRES_ACK[2])); const unacknowledgedLicense = JSON.stringify({ - license: { type: 'basic' } + license: { type: 'basic' }, }); - server.respond(UPLOAD_LICENSE_REQUIRES_ACK); - await uploadLicense(unacknowledgedLicense, 'trial')( - store.dispatch, - null, - services - ); + await uploadLicense(unacknowledgedLicense, 'trial')(store.dispatch, null, services); const rendered = mountWithIntl(component); expect(rendered).toMatchSnapshot(); }); + it('should refresh xpack info and navigate to BASE_PATH when ES accepts new license', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_SUCCESS[2])); const validLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_SUCCESS); await uploadLicense(validLicense)(store.dispatch, null, services); - expect(services.xPackInfo.refresh).toHaveBeenCalled(); - expect(services.kbnUrl.change).toHaveBeenCalledWith(BASE_PATH); + expect(services.legacy.refreshXpack).toHaveBeenCalled(); + expect(services.history.replace).toHaveBeenCalled(); }); + it('should display error when ES returns error', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_TLS_NOT_ENABLED[2])); const rendered = mountWithIntl(component); const license = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_TLS_NOT_ENABLED); await uploadLicense(license)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/license_management/__jest__/util/util.js b/x-pack/legacy/plugins/license_management/__jest__/util/util.js index 537c8d2856079..57d4bf8b3ebd6 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/util/util.js +++ b/x-pack/legacy/plugins/license_management/__jest__/util/util.js @@ -5,9 +5,10 @@ */ import { Provider } from 'react-redux'; -import { licenseManagementStore } from '../../public/store/store'; +import { licenseManagementStore } from '../../public/np_ready/application/store/store'; import React from 'react'; import { mountWithIntl } from '../../../../../test_utils/enzyme_helpers'; +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; const highExpirationMillis = new Date('October 13, 2099 00:00:00Z').getTime(); @@ -22,7 +23,10 @@ export const createMockLicense = ( }; }; export const getComponent = (initialState, Component) => { - const store = licenseManagementStore(initialState); + const services = { + http: httpServiceMock.createSetupContract() + }; + const store = licenseManagementStore(initialState, services); return mountWithIntl( diff --git a/x-pack/legacy/plugins/license_management/common/constants/base_path.js b/x-pack/legacy/plugins/license_management/common/constants/base_path.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/base_path.js rename to x-pack/legacy/plugins/license_management/common/constants/base_path.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/external_links.js b/x-pack/legacy/plugins/license_management/common/constants/external_links.ts similarity index 88% rename from x-pack/legacy/plugins/license_management/common/constants/external_links.js rename to x-pack/legacy/plugins/license_management/common/constants/external_links.ts index 66442aaee8d4a..1ba88b2718ae6 100644 --- a/x-pack/legacy/plugins/license_management/common/constants/external_links.js +++ b/x-pack/legacy/plugins/license_management/common/constants/external_links.ts @@ -9,5 +9,5 @@ const ELASTIC_BASE_URL = 'https://www.elastic.co/'; export const EXTERNAL_LINKS = { SUBSCRIPTIONS: `${ELASTIC_BASE_URL}subscriptions`, TRIAL_EXTENSION: `${ELASTIC_BASE_URL}trialextension`, - TRIAL_LICENSE: `${ELASTIC_BASE_URL}legal/trial_license` + TRIAL_LICENSE: `${ELASTIC_BASE_URL}legal/trial_license`, }; diff --git a/x-pack/legacy/plugins/license_management/common/constants/index.js b/x-pack/legacy/plugins/license_management/common/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/index.js rename to x-pack/legacy/plugins/license_management/common/constants/index.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/permissions.js b/x-pack/legacy/plugins/license_management/common/constants/permissions.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/permissions.js rename to x-pack/legacy/plugins/license_management/common/constants/permissions.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/plugin.ts b/x-pack/legacy/plugins/license_management/common/constants/plugin.ts new file mode 100644 index 0000000000000..14b591e3834ef --- /dev/null +++ b/x-pack/legacy/plugins/license_management/common/constants/plugin.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 { i18n } from '@kbn/i18n'; + +export const PLUGIN = { + TITLE: i18n.translate('xpack.licenseMgmt.managementSectionDisplayName', { + defaultMessage: 'License Management', + }), + ID: 'license_management', +}; diff --git a/x-pack/legacy/plugins/license_management/index.js b/x-pack/legacy/plugins/license_management/index.js deleted file mode 100644 index 1c69b5d96aad6..0000000000000 --- a/x-pack/legacy/plugins/license_management/index.js +++ /dev/null @@ -1,38 +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 { PLUGIN } from './common/constants'; -import { - registerLicenseRoute, - registerStartTrialRoutes, - registerStartBasicRoute, - registerPermissionsRoute -} from './server/routes/api/license/'; -import { createRouter } from '../../server/lib/create_router'; - -export function licenseManagement(kibana) { - return new kibana.Plugin({ - id: PLUGIN.ID, - configPrefix: 'xpack.license_management', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch'], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: [ - 'plugins/license_management', - ] - }, - init: (server) => { - const xpackInfo = server.plugins.xpack_main.info; - const router = createRouter(server, PLUGIN.ID, '/api/license'); - registerLicenseRoute(router, xpackInfo); - registerStartTrialRoutes(router, xpackInfo); - registerStartBasicRoute(router, xpackInfo); - registerPermissionsRoute(router, xpackInfo); - } - }); -} diff --git a/x-pack/legacy/plugins/license_management/index.ts b/x-pack/legacy/plugins/license_management/index.ts new file mode 100644 index 0000000000000..c621a96945c41 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { PLUGIN } from './common/constants'; +import { Legacy } from '../../../../kibana'; +import { plugin } from './server/np_ready'; + +export function licenseManagement(kibana: any) { + return new kibana.Plugin({ + id: PLUGIN.ID, + configPrefix: 'xpack.license_management', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch'], + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/np_ready/application/index.scss'), + managementSections: ['plugins/license_management/legacy'], + }, + init: (server: Legacy.Server) => { + plugin({} as any).setup(server.newPlatform.setup.core, { + ...server.newPlatform.setup.plugins, + __LEGACY: { + xpackMain: server.plugins.xpack_main, + elasticsearch: server.plugins.elasticsearch, + }, + }); + }, + }); +} diff --git a/x-pack/legacy/plugins/license_management/public/index.js b/x-pack/legacy/plugins/license_management/public/legacy.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/public/index.js rename to x-pack/legacy/plugins/license_management/public/legacy.ts diff --git a/x-pack/legacy/plugins/license_management/public/lib/es.js b/x-pack/legacy/plugins/license_management/public/lib/es.js deleted file mode 100644 index 24dbf659ae657..0000000000000 --- a/x-pack/legacy/plugins/license_management/public/lib/es.js +++ /dev/null @@ -1,71 +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 $ from 'jquery'; - -export function putLicense(license, acknowledge) { - const options = { - url: `${chrome.addBasePath('/api/license')}${acknowledge ? '?acknowledge=true' : ''}`, - data: license, - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'PUT', - }; - - return $.ajax(options); -} - -export function startBasic(acknowledge) { - const options = { - url: `${chrome.addBasePath('/api/license/start_basic')}${acknowledge ? '?acknowledge=true' : ''}`, - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - -export function startTrial() { - const options = { - url: chrome.addBasePath('/api/license/start_trial'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - -export function canStartTrial() { - const options = { - url: chrome.addBasePath('/api/license/start_trial'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'GET', - }; - - return $.ajax(options); -} - -export function getPermissions() { - const options = { - url: chrome.addBasePath('/api/license/permissions'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - diff --git a/x-pack/legacy/plugins/license_management/public/main.html b/x-pack/legacy/plugins/license_management/public/main.html deleted file mode 100644 index 310eda8be763a..0000000000000 --- a/x-pack/legacy/plugins/license_management/public/main.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/x-pack/legacy/plugins/license_management/public/management_section.js b/x-pack/legacy/plugins/license_management/public/management_section.ts similarity index 62% rename from x-pack/legacy/plugins/license_management/public/management_section.js rename to x-pack/legacy/plugins/license_management/public/management_section.ts index 8f5cc8a2226dc..4f69581fff0df 100644 --- a/x-pack/legacy/plugins/license_management/public/management_section.js +++ b/x-pack/legacy/plugins/license_management/public/management_section.ts @@ -5,15 +5,11 @@ */ import { management } from 'ui/management'; -import { BASE_PATH } from '../common/constants'; -import { i18n } from '@kbn/i18n'; +import { BASE_PATH, PLUGIN } from '../common/constants'; management.getSection('elasticsearch').register('license_management', { visible: true, - display: i18n.translate('xpack.licenseMgmt.managementSectionDisplayName', { - defaultMessage: 'License Management', - }), + display: PLUGIN.TITLE, order: 99, - url: `#${BASE_PATH}home` + url: `#${BASE_PATH}home`, }); - diff --git a/x-pack/legacy/plugins/license_management/public/_license_management.scss b/x-pack/legacy/plugins/license_management/public/np_ready/application/_license_management.scss similarity index 100% rename from x-pack/legacy/plugins/license_management/public/_license_management.scss rename to x-pack/legacy/plugins/license_management/public/np_ready/application/_license_management.scss diff --git a/x-pack/legacy/plugins/license_management/public/app.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js similarity index 94% rename from x-pack/legacy/plugins/license_management/public/app.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js index eac12710aeedc..ab81f7ff14edd 100644 --- a/x-pack/legacy/plugins/license_management/public/app.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { App as PresentationComponent } from './app'; -import { getPermission, isPermissionsLoading, getPermissionsError } from './store/reducers/licenseManagement'; +import { getPermission, isPermissionsLoading, getPermissionsError } from './store/reducers/license_management'; import { loadPermissions } from './store/actions/permissions'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/app.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.js similarity index 90% rename from x-pack/legacy/plugins/license_management/public/app.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/app.js index ff3a2d6196d76..70e35e0bd8298 100644 --- a/x-pack/legacy/plugins/license_management/public/app.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.js @@ -6,9 +6,9 @@ import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LicenseDashboard, UploadLicense } from './sections/'; +import { LicenseDashboard, UploadLicense } from './sections'; import { Switch, Route } from 'react-router-dom'; -import { BASE_PATH, APP_PERMISSION } from '../common/constants'; +import { APP_PERMISSION } from '../../../common/constants'; import { EuiPageBody, EuiEmptyPrompt, EuiText, EuiLoadingSpinner, EuiCallOut } from '@elastic/eui'; export class App extends Component { @@ -84,8 +84,10 @@ export class App extends Component { return ( - - + + + {/* Match all */} + ); diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx b/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx new file mode 100644 index 0000000000000..d0bf5deacfac1 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider } from 'react-redux'; +import { HashRouter } from 'react-router-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; +import * as history from 'history'; +import { DocLinksStart, HttpSetup, ToastsSetup, ChromeStart } from 'src/core/public'; + +// @ts-ignore +import { App } from './app.container'; +// @ts-ignore +import { licenseManagementStore } from './store'; + +import { setDocLinks } from './lib/docs_links'; +import { BASE_PATH } from '../../../common/constants'; +import { Breadcrumb } from './breadcrumbs'; + +interface AppDependencies { + element: HTMLElement; + chrome: ChromeStart; + + I18nContext: any; + legacy: { + xpackInfo: any; + refreshXpack: () => void; + MANAGEMENT_BREADCRUMB: Breadcrumb; + }; + + toasts: ToastsSetup; + docLinks: DocLinksStart; + http: HttpSetup; +} + +export const boot = (deps: AppDependencies) => { + const { I18nContext, element, legacy, toasts, docLinks, http, chrome } = deps; + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; + const securityDocumentationLink = `${esBase}/security-settings.html`; + + const initialState = { license: legacy.xpackInfo.get('license') }; + + setDocLinks({ securityDocumentationLink }); + + const services = { + legacy: { + refreshXpack: legacy.refreshXpack, + xPackInfo: legacy.xpackInfo, + }, + // So we can imperatively control the hash route + history: history.createHashHistory({ basename: BASE_PATH }), + toasts, + http, + chrome, + MANAGEMENT_BREADCRUMB: legacy.MANAGEMENT_BREADCRUMB, + }; + + const store = licenseManagementStore(initialState, services); + render( + + + + + + + , + element + ); + + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/license_management/public/breadcrumbs.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts similarity index 52% rename from x-pack/legacy/plugins/license_management/public/breadcrumbs.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts index 0de04f4bd70fc..2da04b22c0386 100644 --- a/x-pack/legacy/plugins/license_management/public/breadcrumbs.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts @@ -5,28 +5,33 @@ */ import { i18n } from '@kbn/i18n'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { BASE_PATH } from '../common/constants'; -export function getDashboardBreadcrumbs() { +import { BASE_PATH } from '../../../common/constants'; + +export interface Breadcrumb { + text: string; + href: string; +} + +export function getDashboardBreadcrumbs(root: Breadcrumb) { return [ - MANAGEMENT_BREADCRUMB, + root, { text: i18n.translate('xpack.licenseMgmt.dashboard.breadcrumb', { - defaultMessage: 'License management' + defaultMessage: 'License management', }), - href: `#${BASE_PATH}home` - } + href: `#${BASE_PATH}home`, + }, ]; } -export function getUploadBreadcrumbs() { +export function getUploadBreadcrumbs(root: Breadcrumb) { return [ - ...getDashboardBreadcrumbs(), + ...getDashboardBreadcrumbs(root), { text: i18n.translate('xpack.licenseMgmt.upload.breadcrumb', { - defaultMessage: 'Upload' - }) - } + defaultMessage: 'Upload', + }), + }, ]; } diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/index.js diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/telemetry_opt_in.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/telemetry_opt_in.js diff --git a/x-pack/legacy/plugins/license_management/public/index.scss b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss similarity index 90% rename from x-pack/legacy/plugins/license_management/public/index.scss rename to x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss index 1887a9b48f365..4fb8aafcca93c 100644 --- a/x-pack/legacy/plugins/license_management/public/index.scss +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss @@ -10,4 +10,4 @@ // licChart__legend--small // licChart__legend-isLoading -@import 'license_management'; \ No newline at end of file +@import 'license_management'; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/index.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts similarity index 88% rename from x-pack/legacy/plugins/infra/server/lib/adapters/configuration/index.ts rename to x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts index 4e09b5d0e9e2d..1f963d7f8fcce 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/configuration/index.ts +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './adapter_types'; +export * from './boot'; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts new file mode 100644 index 0000000000000..761fcd2674df6 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.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. + */ + +let docLinks: Record = {}; + +export const setDocLinks = (links: Record) => { + docLinks = links; +}; + +export const getDocLinks = () => docLinks; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts new file mode 100644 index 0000000000000..3924de2202d51 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.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 { HttpSetup } from 'src/core/public'; + +const BASE_PATH = '/api/license'; + +export function putLicense(http: HttpSetup, license: string, acknowledge: boolean) { + return http.put(BASE_PATH, { + query: { + acknowledge: acknowledge ? 'true' : '', + }, + body: license, + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function startBasic(http: HttpSetup, acknowledge: boolean) { + return http.post(`${BASE_PATH}/start_basic`, { + query: { + acknowledge: acknowledge ? 'true' : '', + }, + headers: { + contentType: 'application/json', + }, + body: null, + cache: 'no-cache', + }); +} + +export function startTrial(http: HttpSetup) { + return http.post(`${BASE_PATH}/start_trial`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function canStartTrial(http: HttpSetup) { + return http.get(`${BASE_PATH}/start_trial`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function getPermissions(http: HttpSetup) { + return http.post(`${BASE_PATH}/permissions`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} diff --git a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js similarity index 68% rename from x-pack/legacy/plugins/license_management/public/lib/telemetry.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js index 61d0322227d8e..220807c2a0ac4 100644 --- a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fetchTelemetry } from '../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry'; -export { PRIVACY_STATEMENT_URL } from '../../../../../../src/legacy/core_plugins/telemetry/common/constants'; -export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/telemetry/public/services'; -export { OptInExampleFlyout } from '../../../../../../src/legacy/core_plugins/telemetry/public/components'; +import { fetchTelemetry } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry'; +export { PRIVACY_STATEMENT_URL } from '../../../../../../../../src/legacy/core_plugins/telemetry/common/constants'; +export { TelemetryOptInProvider } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/services'; +export { OptInExampleFlyout } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/components'; let telemetryEnabled; let httpClient; diff --git a/x-pack/legacy/plugins/license_management/public/sections/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js similarity index 94% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js index 3c0402f974f06..bd876ff89b1ad 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import { BASE_PATH } from '../../../../common/constants'; +import { BASE_PATH } from '../../../../../../common/constants'; import { EuiCard, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/index.js diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js new file mode 100644 index 0000000000000..b8db24e65e3bd --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js @@ -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 { LicenseDashboard } from './license_dashboard.container'; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js new file mode 100644 index 0000000000000..4073c5cd52fbb --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js @@ -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 { connect } from 'react-redux'; +import { LicenseDashboard as PresentationComponent } from './license_dashboard'; +import { setBreadcrumb } from '../../store/actions/set_breadcrumb'; + +const mapDispatchToProps = { + setBreadcrumb, +}; + +export const LicenseDashboard = connect(null, mapDispatchToProps)(PresentationComponent); diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js similarity index 88% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js index 4b74211fe8390..e24c167b3b5ad 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js @@ -18,7 +18,8 @@ import { EuiSpacer } from '@elastic/eui'; -export const LicenseDashboard = () => { +export const LicenseDashboard = ({ setBreadcrumb } = { setBreadcrumb: () => {} }) => { + setBreadcrumb('dashboard'); return (
diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js similarity index 95% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js index a282fdc10ddfb..07847a027452f 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js @@ -6,7 +6,7 @@ import { LicenseStatus as PresentationComponent } from './license_status'; import { connect } from 'react-redux'; -import { getLicense, getExpirationDateFormatted, isExpired } from '../../../store/reducers/licenseManagement'; +import { getLicense, getExpirationDateFormatted, isExpired } from '../../../store/reducers/license_management'; import { i18n } from '@kbn/i18n'; const mapStateToProps = (state) => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js similarity index 92% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js index 96348d4fcb146..e01bcdee2fae6 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js @@ -9,7 +9,7 @@ import { connect } from 'react-redux'; import { RequestTrialExtension as PresentationComponent } from './request_trial_extension'; import { shouldShowRequestTrialExtension -} from '../../../store/reducers/licenseManagement'; +} from '../../../store/reducers/license_management'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js similarity index 96% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js index 372dd5ba5afea..106b1a0d24aec 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js @@ -8,7 +8,7 @@ import React from 'react'; import { EuiFlexItem, EuiCard, EuiLink, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; export const RequestTrialExtension = ({ shouldShowRequestTrialExtension }) => { if (!shouldShowRequestTrialExtension) { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js similarity index 95% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js index bd7f7c28be26f..2bb67835c9c31 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js @@ -12,7 +12,7 @@ import { getLicenseType, shouldShowRevertToBasicLicense, getStartBasicMessages -} from '../../../store/reducers/licenseManagement'; +} from '../../../store/reducers/license_management'; import { startBasicLicense, cancelStartBasicLicense } from '../../../store/actions/start_basic'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js similarity index 98% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js index 32ab337359a9b..0e3cea9982a9b 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js @@ -16,7 +16,7 @@ import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; export class RevertToBasic extends React.PureComponent { cancel = () => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js similarity index 97% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js index d8b71688fd537..41a60e38c0932 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { StartTrial as PresentationComponent } from './start_trial'; import { loadTrialStatus, startLicenseTrial } from '../../../store/actions/start_trial'; -import { shouldShowStartTrial } from '../../../store/reducers/licenseManagement'; +import { shouldShowStartTrial } from '../../../store/reducers/license_management'; const mapStateToProps = (state) => { return { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js similarity index 96% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js index 20b130d80a211..4cbd64bdea31f 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js @@ -22,13 +22,11 @@ import { EuiModalHeaderTitle } from '@elastic/eui'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { TelemetryOptIn } from '../../../components/telemetry_opt_in'; import { optInToTelemetry } from '../../../lib/telemetry'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; -const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; -const securityDocumentationLink = `${esBase}/security-settings.html`; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; +import { getDocLinks } from '../../../lib/docs_links'; export class StartTrial extends React.PureComponent { @@ -134,7 +132,7 @@ export class StartTrial extends React.PureComponent { authenticationTypeList: 'AD/LDAP, SAML, PKI, SAML/SSO', securityDocumentationLinkText: ( { @@ -148,16 +149,20 @@ export class UploadLicense extends React.PureComponent { - - } - onChange={this.handleFile} - /> - + + + + } + onChange={this.handleFile} + /> + + + { diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/add_error_message.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_error_message.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/add_error_message.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_error_message.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/add_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_license.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/add_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_license.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/index.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/permissions.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js similarity index 86% rename from x-pack/legacy/plugins/license_management/public/store/actions/permissions.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js index b9316c0a958c4..1cb55bf334a22 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/permissions.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js @@ -19,10 +19,10 @@ export const permissionsError = createAction( 'LICENSE_MANAGEMENT_PERMISSIONS_ERROR' ); -export const loadPermissions = () => async dispatch => { +export const loadPermissions = () => async (dispatch, getState, { http }) => { dispatch(permissionsLoading(true)); try { - const permissions = await getPermissions(); + const permissions = await getPermissions(http); dispatch(permissionsLoading(false)); dispatch(permissionsSuccess(permissions.hasPermission)); } catch (e) { diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts new file mode 100644 index 0000000000000..bcb4a907bdf88 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts @@ -0,0 +1,22 @@ +/* + * 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 { ThunkAction } from 'redux-thunk'; +import { ChromeStart } from 'src/core/public'; +import { getDashboardBreadcrumbs, getUploadBreadcrumbs, Breadcrumb } from '../../breadcrumbs'; + +export const setBreadcrumb = ( + section: 'dashboard' | 'upload' +): ThunkAction => ( + dispatch, + getState, + { chrome, MANAGEMENT_BREADCRUMB } +) => { + if (section === 'upload') { + chrome.setBreadcrumbs(getUploadBreadcrumbs(MANAGEMENT_BREADCRUMB)); + } else { + chrome.setBreadcrumbs(getDashboardBreadcrumbs(MANAGEMENT_BREADCRUMB)); + } +}; diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js similarity index 88% rename from x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js index b144729c1a317..cc52916a30d8b 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { createAction } from 'redux-actions'; import { startBasic } from '../../lib/es'; -import { toastNotifications } from 'ui/notify'; -import { i18n } from '@kbn/i18n'; export const startBasicLicenseStatus = createAction( 'LICENSE_MANAGEMENT_START_BASIC_LICENSE_STATUS' @@ -20,17 +19,17 @@ export const cancelStartBasicLicense = createAction( export const startBasicLicense = (currentLicenseType, ack) => async ( dispatch, getState, - { xPackInfo, $injector } + { legacy: { refreshXpack }, toasts, http } ) => { /*eslint camelcase: 0*/ - const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic(ack); + const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic(http, ack); if (acknowledged) { if (basic_was_started) { - await xPackInfo.refresh($injector); + await refreshXpack(); // reload necessary to get left nav to refresh with proper links window.location.reload(); } else { - return toastNotifications.addDanger(error_message); + return toasts.addDanger(error_message); } } else { //messages coming back in arrays diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js similarity index 74% rename from x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js index 0bc274366d97e..e3f33a0cdc977 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js @@ -6,29 +6,28 @@ import { createAction } from 'redux-actions'; import { canStartTrial, startTrial } from '../../lib/es'; -import { toastNotifications } from 'ui/notify'; export const trialStatusLoaded = createAction( 'LICENSE_MANAGEMENT_TRIAL_STATUS_LOADED' ); -export const loadTrialStatus = () => async dispatch => { - const trialOK = await canStartTrial(); +export const loadTrialStatus = () => async (dispatch, getState, { http }) => { + const trialOK = await canStartTrial(http); dispatch(trialStatusLoaded(trialOK)); }; export const startLicenseTrial = () => async ( dispatch, getState, - { xPackInfo, $injector } + { legacy: { refreshXpack }, toasts, http } ) => { /*eslint camelcase: 0*/ - const { trial_was_started, error_message } = await startTrial(); + const { trial_was_started, error_message } = await startTrial(http); if (trial_was_started) { - await xPackInfo.refresh($injector); + await refreshXpack(); // reload necessary to get left nav to refresh with proper links window.location.reload(); } else { - return toastNotifications.addDanger(error_message); + return toasts.addDanger(error_message); } }; diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js similarity index 90% rename from x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js index be5b3125a6a12..e93e891f6de93 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js @@ -5,8 +5,7 @@ */ import { createAction } from 'redux-actions'; -import { addLicense } from '../actions/add_license'; -import { BASE_PATH } from '../../../common/constants/base_path'; +import { addLicense } from './add_license'; import { putLicense } from '../../lib/es'; import { addUploadErrorMessage } from './add_error_message'; import { i18n } from '@kbn/i18n'; @@ -17,7 +16,19 @@ const genericUploadError = i18n.translate('xpack.licenseMgmt.uploadLicense.gener defaultMessage: 'Error encountered uploading license:' }); -const dispatchFromResponse = async (response, dispatch, currentLicenseType, newLicenseType, { xPackInfo, kbnUrl, $injector }) => { +const dispatchFromResponse = async ( + response, + dispatch, + currentLicenseType, + newLicenseType, + { + history, + legacy: { + xPackInfo, + refreshXpack, + }, + }, +) => { const { error, acknowledged, license_status: licenseStatus, acknowledge } = response; if (error) { dispatch(uploadLicenseStatus({})); @@ -34,10 +45,10 @@ const dispatchFromResponse = async (response, dispatch, currentLicenseType, newL defaultMessage: 'The supplied license has expired.' }))); } else { - await xPackInfo.refresh($injector); + await refreshXpack(); dispatch(addLicense(xPackInfo.get('license'))); dispatch(uploadLicenseStatus({})); - kbnUrl.change(BASE_PATH); + history.replace('/home'); // reload necessary to get left nav to refresh with proper links window.location.reload(); } @@ -75,7 +86,7 @@ export const uploadLicense = (licenseString, currentLicenseType, acknowledge) => )); } try { - const response = await putLicense(licenseString, acknowledge); + const response = await putLicense(services.http, licenseString, acknowledge); await dispatchFromResponse(response, dispatch, currentLicenseType, newLicenseType, services); } catch (err) { const message = (err.responseJSON && err.responseJSON.error.reason) ? err.responseJSON.error.reason : i18n.translate( diff --git a/x-pack/legacy/plugins/license_management/public/store/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/index.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js similarity index 80% rename from x-pack/legacy/plugins/license_management/public/store/reducers/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js index 297e28a320810..7872202272acf 100644 --- a/x-pack/legacy/plugins/license_management/public/store/reducers/index.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licenseManagement } from './licenseManagement'; +export { licenseManagement } from './license_management'; diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license_management.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license_management.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/permissions.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/permissions.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/permissions.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/permissions.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/start_basic_license_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/start_basic_license_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/start_basic_license_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/start_basic_license_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/trial_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/trial_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/trial_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/trial_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/upload_error_message.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_error_message.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/upload_error_message.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_error_message.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/upload_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/upload_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/store.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js similarity index 93% rename from x-pack/legacy/plugins/license_management/public/store/store.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js index 87ccf4e2aba46..b3e6bfbe5db73 100644 --- a/x-pack/legacy/plugins/license_management/public/store/store.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js @@ -7,7 +7,7 @@ import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; -import { licenseManagement } from './reducers/'; +import { licenseManagement } from './reducers'; export const licenseManagementStore = (initialState = {}, services = {}) => { const enhancers = [ applyMiddleware(thunk.withExtraArgument(services)) ]; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/index.ts b/x-pack/legacy/plugins/license_management/public/np_ready/index.ts new file mode 100644 index 0000000000000..59e2f02d8cb52 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/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 'src/core/public'; +import { LicenseManagementUIPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new LicenseManagementUIPlugin(); diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts new file mode 100644 index 0000000000000..abd658ff91db0 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { PLUGIN } from '../../common/constants'; +import { Breadcrumb } from './application/breadcrumbs'; + +export interface Plugins { + __LEGACY: { + xpackInfo: XPackMainPlugin; + refreshXpack: () => void; + MANAGEMENT_BREADCRUMB: Breadcrumb; + }; +} + +export class LicenseManagementUIPlugin implements Plugin { + setup({ application, notifications, http }: CoreSetup, { __LEGACY }: Plugins) { + application.register({ + id: PLUGIN.ID, + title: PLUGIN.TITLE, + async mount( + { + core: { + docLinks, + i18n: { Context: I18nContext }, + chrome, + }, + }, + { element } + ) { + const { boot } = await import('./application'); + return boot({ + legacy: { ...__LEGACY }, + I18nContext, + toasts: notifications.toasts, + docLinks, + http, + element, + chrome, + }); + }, + }); + } + start(core: CoreStart, plugins: any) {} + stop() {} +} diff --git a/x-pack/legacy/plugins/license_management/public/register_route.js b/x-pack/legacy/plugins/license_management/public/register_route.js deleted file mode 100644 index fcf1b85f95b8a..0000000000000 --- a/x-pack/legacy/plugins/license_management/public/register_route.js +++ /dev/null @@ -1,109 +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 { render, unmountComponentAtNode } from 'react-dom'; -import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; -import { setTelemetryOptInService, setTelemetryEnabled, setHttpClient, TelemetryOptInProvider } from './lib/telemetry'; -import { I18nContext } from 'ui/i18n'; -import chrome from 'ui/chrome'; - -import { App } from './app.container'; -import { BASE_PATH } from '../common/constants/base_path'; - -import routes from 'ui/routes'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; - -import template from './main.html'; -import { licenseManagementStore } from './store'; -import { getDashboardBreadcrumbs, getUploadBreadcrumbs } from './breadcrumbs'; - -const renderReact = (elem, store) => { - render( - - - - - - - , - elem - ); -}; - -/* - This method handles the cleanup needed when route is scope is destroyed. It also prevents Angular - from destroying scope when route changes and both old route and new route are this same route. -*/ -const manageAngularLifecycle = ($scope, $route, elem) => { - const lastRoute = $route.current; - const deregister = $scope.$on('$locationChangeSuccess', () => { - const currentRoute = $route.current; - // if templates are the same we are on the same route - if (lastRoute.$$route.template === currentRoute.$$route.template) { - // update the breadcrumbs by re-running the k7Breadcrumbs function - chrome.breadcrumbs.set(currentRoute.$$route.k7Breadcrumbs($route)); - // this prevents angular from destroying scope - $route.current = lastRoute; - } - }); - $scope.$on('$destroy', () => { - deregister && deregister(); - // manually unmount component when scope is destroyed - elem && unmountComponentAtNode(elem); - }); -}; -const initializeTelemetry = ($injector) => { - const telemetryEnabled = $injector.get('telemetryEnabled'); - const Private = $injector.get('Private'); - const telemetryOptInProvider = Private(TelemetryOptInProvider); - setTelemetryOptInService(telemetryOptInProvider); - setTelemetryEnabled(telemetryEnabled); - setHttpClient($injector.get('$http')); -}; -routes - .when(`${BASE_PATH}:view?`, { - template: template, - k7Breadcrumbs($route) { - switch ($route.current.params.view) { - case 'upload_license': - return getUploadBreadcrumbs(); - default: - return getDashboardBreadcrumbs(); - } - }, - controllerAs: 'licenseManagement', - controller: class LicenseManagementController { - - constructor($injector, $rootScope, $scope, $route, kbnUrl) { - initializeTelemetry($injector); - let autoLogout = null; - /* if security is disabled, there will be no autoLogout service, - so just substitute noop function in that case */ - try { - autoLogout = $injector.get('autoLogout'); - } catch (e) { - autoLogout = () => {}; - } - - $scope.$$postDigest(() => { - const elem = document.getElementById('licenseReactRoot'); - const initialState = { license: xpackInfo.get('license') }; - const kbnUrlWrapper = { - change(url) { - kbnUrl.change(url); - $rootScope.$digest(); - } - }; - const services = { autoLogout, xPackInfo: xpackInfo, kbnUrl: kbnUrlWrapper, $injector }; - const store = licenseManagementStore(initialState, services); - renderReact(elem, store); - manageAngularLifecycle($scope, $route, elem); - }); - } - } - }); diff --git a/x-pack/legacy/plugins/license_management/public/register_route.ts b/x-pack/legacy/plugins/license_management/public/register_route.ts new file mode 100644 index 0000000000000..ad84f28c6b6d7 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/register_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 { App } from 'src/core/public'; + +/* Legacy Imports */ +import { npSetup, npStart } from 'ui/new_platform'; +import { MANAGEMENT_BREADCRUMB } from 'ui/management'; +import routes from 'ui/routes'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; + +import { plugin } from './np_ready'; + +import { + setTelemetryOptInService, + setTelemetryEnabled, + setHttpClient, + TelemetryOptInProvider, + // @ts-ignore +} from './np_ready/application/lib/telemetry'; + +import { BASE_PATH } from '../common/constants'; + +/* + This method handles the cleanup needed when route is scope is destroyed. It also prevents Angular + from destroying scope when route changes and both old route and new route are this same route. +*/ +const manageAngularLifecycle = ($scope: any, $route: any, unmount: () => void) => { + const lastRoute = $route.current; + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + // if templates are the same we are on the same route + if (lastRoute.$$route.template === currentRoute.$$route.template) { + // this prevents angular from destroying scope + $route.current = lastRoute; + } + }); + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + unmount(); + }); +}; + +const initializeTelemetry = ($injector: any) => { + const telemetryEnabled = $injector.get('telemetryEnabled'); + const Private = $injector.get('Private'); + const telemetryOptInProvider = Private(TelemetryOptInProvider); + setTelemetryOptInService(telemetryOptInProvider); + setTelemetryEnabled(telemetryEnabled); + setHttpClient($injector.get('$http')); +}; + +const template = ` +
+
`; + +routes.when(`${BASE_PATH}:view?`, { + template, + controllerAs: 'licenseManagement', + controller: class LicenseManagementController { + constructor($injector: any, $rootScope: any, $scope: any, $route: any) { + initializeTelemetry($injector); + + $scope.$$postDigest(() => { + const element = document.getElementById('licenseReactRoot')!; + + const refreshXpack = async () => { + await xpackInfo.refresh($injector); + }; + + plugin({} as any).setup( + { + ...npSetup.core, + application: { + ...npSetup.core.application, + async register(app: App) { + const unmountApp = await app.mount({ ...npStart } as any, { + element, + appBasePath: '', + }); + manageAngularLifecycle($scope, $route, unmountApp as any); + }, + }, + }, + { + __LEGACY: { xpackInfo, refreshXpack, MANAGEMENT_BREADCRUMB }, + } + ); + }); + } + } as any, +} as any); diff --git a/x-pack/legacy/plugins/license_management/server/lib/license.js b/x-pack/legacy/plugins/license_management/server/lib/license.js deleted file mode 100644 index bc52fe7f56b34..0000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/license.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. - */ - -const getLicensePath = (acknowledge) => `/_license${ acknowledge ? '?acknowledge=true' : ''}`; - -export async function putLicense(req, xpackInfo) { - const { acknowledge } = req.query; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: getLicensePath(acknowledge), - body: req.payload - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - const { acknowledged, license_status: licenseStatus } = response; - if (acknowledged && licenseStatus === 'valid') { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/lib/start_basic.js b/x-pack/legacy/plugins/license_management/server/lib/start_basic.js deleted file mode 100644 index 0924146b70280..0000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/start_basic.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. - */ - -const getStartBasicPath = (acknowledge) => `/_license/start_basic${ acknowledge ? '?acknowledge=true' : ''}`; - - -export async function startBasic(req, xpackInfo) { - const { acknowledge } = req.query; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: getStartBasicPath(acknowledge) - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - /*eslint camelcase: 0*/ - const { basic_was_started } = response; - if (basic_was_started) { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/lib/start_trial.js b/x-pack/legacy/plugins/license_management/server/lib/start_trial.js deleted file mode 100644 index 19b702e4e43c4..0000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/start_trial.js +++ /dev/null @@ -1,38 +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 async function canStartTrial(req) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'GET', - path: '/_license/trial_status' - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - const { eligible_to_start_trial } = response; - return eligible_to_start_trial; - } catch (error) { - return error.body; - } -} -export async function startTrial(req, xpackInfo) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: '/_license/start_trial?acknowledge=true' - }; - try { - /*eslint camelcase: 0*/ - const response = await callWithRequest(req, 'transport.request', options); - const { trial_was_started } = response; - if (trial_was_started) { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js b/x-pack/legacy/plugins/license_management/server/np_ready/index.ts similarity index 53% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js rename to x-pack/legacy/plugins/license_management/server/np_ready/index.ts index fd7bfcf8d7c2a..2ad4143a94730 100644 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js +++ b/x-pack/legacy/plugins/license_management/server/np_ready/index.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { startBasic } from '../../../lib/start_basic'; +import { PluginInitializerContext } from 'src/core/server'; +import { LicenseManagementServerPlugin } from './plugin'; -export function registerStartBasicRoute(router, xpackInfo) { - router.post('/start_basic', (request) => { - return startBasic(request, xpackInfo); - }); -} +export const plugin = (ctx: PluginInitializerContext) => new LicenseManagementServerPlugin(); diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts new file mode 100644 index 0000000000000..b52c9d50170b9 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.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 { KibanaRequest } from 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; +const getLicensePath = (acknowledge: boolean) => + `/_license${acknowledge ? '?acknowledge=true' : ''}`; + +export async function putLicense( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { acknowledge } = req.query; + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: getLicensePath(Boolean(acknowledge)), + body: req.body, + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { acknowledged, license_status: licenseStatus } = response; + if (acknowledged && licenseStatus === 'valid') { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/lib/permissions.js b/x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts similarity index 59% rename from x-pack/legacy/plugins/license_management/server/lib/permissions.js rename to x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts index 11d25a9c2dc24..84cd92821797f 100644 --- a/x-pack/legacy/plugins/license_management/server/lib/permissions.js +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { wrapCustomError } from '../../../../server/lib/create_router/error_wrappers'; - -export async function getPermissions(req, xpackInfo) { - if (!xpackInfo) { - // xpackInfo is updated via poll, so it may not be available until polling has begun. - // In this rare situation, tell the client the service is temporarily unavailable. - throw wrapCustomError(new Error('Security info unavailable'), 503); - } +import { KibanaRequest } from 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; +export async function getPermissions( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { const securityInfo = xpackInfo && xpackInfo.isAvailable() && xpackInfo.feature('security'); if (!securityInfo || !securityInfo.isAvailable() || !securityInfo.isEnabled()) { // If security isn't enabled, let the user use license management @@ -21,22 +20,21 @@ export async function getPermissions(req, xpackInfo) { }; } - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); + const { callWithRequest } = elasticsearch.getCluster('admin'); const options = { method: 'POST', path: '/_security/user/_has_privileges', body: { cluster: ['manage'], // License management requires "manage" cluster privileges - } + }, }; try { - const response = await callWithRequest(req, 'transport.request', options); + const response = await callWithRequest(req as any, 'transport.request', options); return { hasPermission: response.cluster.manage, }; } catch (error) { return error.body; } - } diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts new file mode 100644 index 0000000000000..ba042be132d68 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.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 { KibanaRequest } from 'kibana/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; + +const getStartBasicPath = (acknowledge: boolean) => + `/_license/start_basic${acknowledge ? '?acknowledge=true' : ''}`; + +export async function startBasic( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { acknowledge } = req.query; + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: getStartBasicPath(Boolean(acknowledge)), + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { basic_was_started: basicWasStarted } = response; + if (basicWasStarted) { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts new file mode 100644 index 0000000000000..3569085d413ca --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.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 { KibanaRequest } from 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; + +export async function canStartTrial( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin +) { + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'GET', + path: '/_license/trial_status', + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + return response.eligible_to_start_trial; + } catch (error) { + return error.body; + } +} + +export async function startTrial( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: '/_license/start_trial?acknowledge=true', + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { trial_was_started: trialWasStarted } = response; + if (trialWasStarted) { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts b/x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts new file mode 100644 index 0000000000000..9f065cf98d715 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/plugin.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 { Plugin, CoreSetup } from 'src/core/server'; +import { Dependencies, Server } from './types'; + +import { + registerLicenseRoute, + registerStartTrialRoutes, + registerStartBasicRoute, + registerPermissionsRoute, +} from './routes/api/license'; + +export class LicenseManagementServerPlugin implements Plugin { + setup({ http }: CoreSetup, { __LEGACY }: Dependencies) { + const xpackInfo = __LEGACY.xpackMain.info; + const router = http.createRouter(); + + const server: Server = { + router, + }; + + const legacy = { plugins: __LEGACY }; + + registerLicenseRoute(server, legacy, xpackInfo); + registerStartTrialRoutes(server, legacy, xpackInfo); + registerStartBasicRoute(server, legacy, xpackInfo); + registerPermissionsRoute(server, legacy, xpackInfo); + } + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/index.js b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/index.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/index.js rename to x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/index.ts diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts new file mode 100644 index 0000000000000..cdc929a2f3bb3 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts @@ -0,0 +1,32 @@ +/* + * 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'; +import { putLicense } from '../../../lib/license'; +import { Legacy, Server } from '../../../types'; + +export function registerLicenseRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.put( + { + path: '/api/license', + validate: { + query: schema.object({ acknowledge: schema.string() }), + body: schema.object({ + license: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await putLicense(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts new file mode 100644 index 0000000000000..0f6c343d04fcd --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.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 { getPermissions } from '../../../lib/permissions'; +import { Legacy, Server } from '../../../types'; + +export function registerPermissionsRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.post( + { path: '/api/license/permissions', validate: false }, + async (ctx, request, response) => { + if (!xpackInfo) { + // xpackInfo is updated via poll, so it may not be available until polling has begun. + // In this rare situation, tell the client the service is temporarily unavailable. + return response.customError({ statusCode: 503, body: 'Security info unavailable' }); + } + + try { + return response.ok({ + body: await getPermissions(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts new file mode 100644 index 0000000000000..ee7ac8602104b --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.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 { schema } from '@kbn/config-schema'; +import { startBasic } from '../../../lib/start_basic'; +import { Legacy, Server } from '../../../types'; + +export function registerStartBasicRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.post( + { + path: '/api/license/start_basic', + validate: { query: schema.object({ acknowledge: schema.string() }) }, + }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await startBasic(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts new file mode 100644 index 0000000000000..d93f13eba363a --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.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 { canStartTrial, startTrial } from '../../../lib/start_trial'; +import { Legacy, Server } from '../../../types'; + +export function registerStartTrialRoutes(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.get( + { path: '/api/license/start_trial', validate: false }, + async (ctx, request, response) => { + try { + return response.ok({ body: await canStartTrial(request, legacy.plugins.elasticsearch) }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); + + server.router.post( + { path: '/api/license/start_trial', validate: false }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await startTrial(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/types.ts b/x-pack/legacy/plugins/license_management/server/np_ready/types.ts new file mode 100644 index 0000000000000..a636481323e29 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/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 } from 'src/core/server'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { ElasticsearchPlugin } from '../../../../../../src/legacy/core_plugins/elasticsearch'; + +export interface Dependencies { + __LEGACY: { + xpackMain: XPackMainPlugin; + elasticsearch: ElasticsearchPlugin; + }; +} + +export interface Server { + router: IRouter; +} + +export interface Legacy { + plugins: Dependencies['__LEGACY']; +} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js b/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js deleted file mode 100644 index d8fd4e5abd8f2..0000000000000 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js +++ /dev/null @@ -1,13 +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 { getPermissions } from '../../../lib/permissions'; - -export function registerPermissionsRoute(router, xpackInfo) { - router.post('/permissions', (request) => { - return getPermissions(request, xpackInfo); - }); -} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js b/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js deleted file mode 100644 index c13ea680fa0da..0000000000000 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js +++ /dev/null @@ -1,16 +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 { canStartTrial, startTrial } from '../../../lib/start_trial'; - -export function registerStartTrialRoutes(router, xpackInfo) { - router.get('/start_trial', (request) => { - return canStartTrial(request); - }); - router.post('/start_trial', (request) => { - return startTrial(request, xpackInfo); - }); -} diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index 3b2f887e13c87..77b57e3fe4965 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -57,6 +57,8 @@ export const FIELD_ORIGIN = { }; export const SOURCE_DATA_ID_ORIGIN = 'source'; +export const META_ID_ORIGIN_SUFFIX = 'meta'; +export const SOURCE_META_ID_ORIGIN = `${SOURCE_DATA_ID_ORIGIN}_${META_ID_ORIGIN_SUFFIX}`; export const GEOJSON_FILE = 'GEOJSON_FILE'; @@ -124,6 +126,11 @@ export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLab export const COUNT_PROP_NAME = 'doc_count'; export const STYLE_TYPE = { - 'STATIC': 'STATIC', - 'DYNAMIC': 'DYNAMIC' + STATIC: 'STATIC', + DYNAMIC: 'DYNAMIC' +}; + +export const LAYER_STYLE_TYPE = { + VECTOR: 'VECTOR', + HEATMAP: 'HEATMAP' }; diff --git a/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.js b/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.js new file mode 100644 index 0000000000000..ed585e013d06f --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.js @@ -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 _ from 'lodash'; +import { LAYER_TYPE, STYLE_TYPE } from '../constants'; + +function isVectorLayer(layerDescriptor) { + const layerType = _.get(layerDescriptor, 'type'); + return layerType === LAYER_TYPE.VECTOR; +} + +export function addFieldMetaOptions({ attributes }) { + if (!attributes.layerListJSON) { + return attributes; + } + + const layerList = JSON.parse(attributes.layerListJSON); + layerList.forEach((layerDescriptor) => { + if (isVectorLayer(layerDescriptor) && _.has(layerDescriptor, 'style.properties')) { + Object.values(layerDescriptor.style.properties).forEach(stylePropertyDescriptor => { + if (stylePropertyDescriptor.type === STYLE_TYPE.DYNAMIC) { + stylePropertyDescriptor.options.fieldMetaOptions = { + isEnabled: false, // turn off field metadata to avoid changing behavior of existing saved objects + sigma: 3, + }; + } + }); + } + }); + + return { + ...attributes, + layerListJSON: JSON.stringify(layerList), + }; +} diff --git a/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.test.js b/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.test.js new file mode 100644 index 0000000000000..905f77223b3bc --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/add_field_meta_options.test.js @@ -0,0 +1,121 @@ +/* + * 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 { addFieldMetaOptions } from './add_field_meta_options'; +import { LAYER_TYPE, STYLE_TYPE } from '../constants'; + +describe('addFieldMetaOptions', () => { + + test('Should handle missing layerListJSON attribute', () => { + const attributes = { + title: 'my map', + }; + expect(addFieldMetaOptions({ attributes })).toEqual({ + title: 'my map', + }); + }); + + test('Should ignore non-vector layers', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.HEATMAP, + style: { + type: 'HEATMAP', + colorRampName: 'Greens' + } + } + ]); + const attributes = { + title: 'my map', + layerListJSON + }; + expect(addFieldMetaOptions({ attributes })).toEqual({ + title: 'my map', + layerListJSON + }); + }); + + test('Should ignore static style properties', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + type: 'VECTOR', + properties: { + lineColor: { + type: STYLE_TYPE.STATIC, + options: { + color: '#FFFFFF' + } + } + } + } + } + ]); + const attributes = { + title: 'my map', + layerListJSON + }; + expect(addFieldMetaOptions({ attributes })).toEqual({ + title: 'my map', + layerListJSON + }); + }); + + test('Should add field meta options to dynamic style properties', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: STYLE_TYPE.DYNAMIC, + options: { + field: { + name: 'my_field', + origin: 'source' + }, + color: 'Greys' + } + } + } + } + } + ]); + const attributes = { + title: 'my map', + layerListJSON + }; + expect(addFieldMetaOptions({ attributes })).toEqual({ + title: 'my map', + layerListJSON: JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: STYLE_TYPE.DYNAMIC, + options: { + field: { + name: 'my_field', + origin: 'source' + }, + color: 'Greys', + fieldMetaOptions: { + isEnabled: false, + sigma: 3, + } + } + } + } + } + } + ]) + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/migrations.js b/x-pack/legacy/plugins/maps/migrations.js index 39dc58f259961..df19c8425199a 100644 --- a/x-pack/legacy/plugins/maps/migrations.js +++ b/x-pack/legacy/plugins/maps/migrations.js @@ -8,6 +8,7 @@ import { extractReferences } from './common/migrations/references'; import { emsRasterTileToEmsVectorTile } from './common/migrations/ems_raster_tile_to_ems_vector_tile'; import { topHitsTimeToSort } from './common/migrations/top_hits_time_to_sort'; import { moveApplyGlobalQueryToSources } from './common/migrations/move_apply_global_query'; +import { addFieldMetaOptions } from './common/migrations/add_field_meta_options'; export const migrations = { 'map': { @@ -37,11 +38,12 @@ export const migrations = { }; }, '7.6.0': (doc) => { - const attributes = moveApplyGlobalQueryToSources(doc); + const attributesPhase1 = moveApplyGlobalQueryToSources(doc); + const attributesPhase2 = addFieldMetaOptions({ attributes: attributesPhase1 }); return { ...doc, - attributes, + attributes: attributesPhase2, }; } }, diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 7169014542710..609f075e194f8 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -6,12 +6,12 @@ import { getRequestInspectorStats, getResponseInspectorStats } from '../../../../../src/legacy/ui/public/courier'; export { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; import { esFilters } from '../../../../../src/plugins/data/public'; +import { npStart } from 'ui/new_platform'; export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; export { SearchSource } from '../../../../../src/legacy/ui/public/courier'; -export const indexPatternService = data.indexPatterns.indexPatterns; +export const indexPatternService = npStart.plugins.data.indexPatterns; export async function fetchSearchSourceAndRecordWithInspector({ searchSource, diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js index eb80169e94eab..af78e3a871802 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ - import { AbstractField } from './field'; import { COUNT_AGG_TYPE } from '../../../common/constants'; +import { isMetricCountable } from '../util/is_metric_countable'; import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property'; export class ESAggMetricField extends AbstractField { @@ -36,6 +36,11 @@ export class ESAggMetricField extends AbstractField { return (this.getAggType() === COUNT_AGG_TYPE) ? true : !!this._esDocField; } + async getDataType() { + // aggregations only provide numerical data + return 'number'; + } + getESDocFieldName() { return this._esDocField ? this._esDocField.getName() : ''; } @@ -55,7 +60,6 @@ export class ESAggMetricField extends AbstractField { ); } - makeMetricAggConfig() { const metricAggConfig = { id: this.getName(), @@ -69,4 +73,13 @@ export class ESAggMetricField extends AbstractField { } return metricAggConfig; } + + supportsFieldMeta() { + // count and sum aggregations are not within field bounds so they do not support field meta. + return !isMetricCountable(this.getAggType()); + } + + async getFieldMetaRequest(config) { + return this._esDocField.getFieldMetaRequest(config); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js new file mode 100644 index 0000000000000..65b8c518fa895 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js @@ -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 { ESAggMetricField } from './es_agg_field'; +import { METRIC_TYPE } from '../../../common/constants'; + +describe('supportsFieldMeta', () => { + + test('Non-counting aggregations should support field meta', () => { + const avgMetric = new ESAggMetricField({ aggType: METRIC_TYPE.AVG }); + expect(avgMetric.supportsFieldMeta()).toBe(true); + const maxMetric = new ESAggMetricField({ aggType: METRIC_TYPE.MAX }); + expect(maxMetric.supportsFieldMeta()).toBe(true); + const minMetric = new ESAggMetricField({ aggType: METRIC_TYPE.MIN }); + expect(minMetric.supportsFieldMeta()).toBe(true); + }); + + test('Counting aggregations should not support field meta', () => { + const countMetric = new ESAggMetricField({ aggType: METRIC_TYPE.COUNT }); + expect(countMetric.supportsFieldMeta()).toBe(false); + const sumMetric = new ESAggMetricField({ aggType: METRIC_TYPE.SUM }); + expect(sumMetric.supportsFieldMeta()).toBe(false); + const uniqueCountMetric = new ESAggMetricField({ aggType: METRIC_TYPE.UNIQUE_COUNT }); + expect(uniqueCountMetric.supportsFieldMeta()).toBe(false); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js index 5cc0c9a29ce02..ad15c6249e554 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js @@ -27,4 +27,31 @@ export class ESDocField extends AbstractField { return field.type; } + supportsFieldMeta() { + return true; + } + + async getFieldMetaRequest(/* config */) { + const field = await this._getField(); + + if (field.type !== 'number' && field.type !== 'date') { + return null; + } + + const extendedStats = {}; + if (field.scripted) { + extendedStats.script = { + source: field.script, + lang: field.lang + }; + } else { + extendedStats.field = this._fieldName; + } + return { + [this._fieldName]: { + extended_stats: extendedStats + } + }; + } + } diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.js b/x-pack/legacy/plugins/maps/public/layers/fields/field.js index b53c6991c6ebe..f1bb116d29c8b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/field.js @@ -42,4 +42,12 @@ export class AbstractField { getOrigin() { return this._origin; } + + supportsFieldMeta() { + return false; + } + + async getFieldMetaRequest(/* config */) { + return null; + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js index 184fdc0663bd7..432492973cce0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js +++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js @@ -7,6 +7,7 @@ import { ESTermSource } from '../sources/es_term_source'; import { getComputedFieldNamePrefix } from '../styles/vector/style_util'; +import { META_ID_ORIGIN_SUFFIX } from '../../../common/constants'; export class InnerJoin { @@ -36,10 +37,14 @@ export class InnerJoin { // Source request id must be static and unique because the re-fetch logic uses the id to locate the previous request. // Elasticsearch sources have a static and unique id so that requests can be modified in the inspector. // Using the right source id as the source request id because it meets the above criteria. - getSourceId() { + getSourceDataRequestId() { return `join_source_${this._rightSource.getId()}`; } + getSourceMetaDataRequestId() { + return `${this.getSourceDataRequestId()}_${META_ID_ORIGIN_SUFFIX}`; + } + getLeftField() { return this._leftField; } diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index 1c2f33df66bf8..b1f3c32f267b9 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -80,7 +80,7 @@ export class AbstractLayer { } supportsElasticsearchFilters() { - return this._source.supportsElasticsearchFilters(); + return this._source.isESSource(); } async supportsFitToBounds() { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 413f99480a8c2..f4cb43ad90146 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -15,7 +15,7 @@ import { AggConfigs } from 'ui/agg_types'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { convertToGeoJson } from './convert_to_geojson'; import { VectorStyle } from '../../styles/vector/vector_style'; -import { vectorStyles } from '../../styles/vector/vector_style_defaults'; +import { getDefaultDynamicProperties, VECTOR_STYLES } from '../../styles/vector/vector_style_defaults'; import { RENDER_AS } from './render_as'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; @@ -170,13 +170,15 @@ export class ESGeoGridSource extends AbstractESAggSource { const searchSource = await this._makeSearchSource(searchFilters, 0); const aggConfigs = new AggConfigs(indexPattern, this._makeAggConfigs(searchFilters.geogridPrecision), aggSchemas.all); searchSource.setField('aggs', aggConfigs.toDsl()); - const esResponse = await this._runEsQuery( - layerName, + const esResponse = await this._runEsQuery({ + requestId: this.getId(), + requestName: layerName, searchSource, registerCancelCallback, - i18n.translate('xpack.maps.source.esGrid.inspectorDescription', { + requestDescription: i18n.translate('xpack.maps.source.esGrid.inspectorDescription', { defaultMessage: 'Elasticsearch geo grid aggregation request' - })); + }), + }); const tabifiedResp = tabifyAggResponse(aggConfigs, esResponse); const { featureCollection } = convertToGeoJson({ @@ -226,10 +228,14 @@ export class ESGeoGridSource extends AbstractESAggSource { sourceDescriptor: this._descriptor, ...options }); + + const defaultDynamicProperties = getDefaultDynamicProperties(); + descriptor.style = VectorStyle.createDescriptor({ - [vectorStyles.FILL_COLOR]: { + [VECTOR_STYLES.FILL_COLOR]: { type: DynamicStyleProperty.type, options: { + ...defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options, field: { label: COUNT_PROP_LABEL, name: COUNT_PROP_NAME, @@ -238,9 +244,10 @@ export class ESGeoGridSource extends AbstractESAggSource { color: 'Blues' } }, - [vectorStyles.ICON_SIZE]: { + [VECTOR_STYLES.ICON_SIZE]: { type: DynamicStyleProperty.type, options: { + ...defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options, field: { label: COUNT_PROP_LABEL, name: COUNT_PROP_NAME, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index 1b446e1f2159a..cc1e53dc5cb3f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -8,13 +8,13 @@ import React, { Fragment, Component } from 'react'; import { RENDER_AS } from './render_as'; import { MetricsEditor } from '../../../components/metrics_editor'; -import { METRIC_TYPE } from '../../../../common/constants'; import { indexPatternService } from '../../../kibana_services'; import { ResolutionEditor } from './resolution_editor'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; +import { isMetricCountable } from '../../util/is_metric_countable'; export class UpdateSourceEditor extends Component { state = { @@ -72,7 +72,7 @@ export class UpdateSourceEditor extends Component { this.props.renderAs === RENDER_AS.HEATMAP ? metric => { //these are countable metrics, where blending heatmap color blobs make sense - return [METRIC_TYPE.COUNT, METRIC_TYPE.SUM, METRIC_TYPE.UNIQUE_COUNT].includes(metric.value); + return isMetricCountable(metric.value); } : null; const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 01220136b14f3..4eb0a952defba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -12,7 +12,7 @@ import { VectorLayer } from '../../vector_layer'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { VectorStyle } from '../../styles/vector/vector_style'; -import { vectorStyles } from '../../styles/vector/vector_style_defaults'; +import { getDefaultDynamicProperties, VECTOR_STYLES } from '../../styles/vector/vector_style_defaults'; import { i18n } from '@kbn/i18n'; import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, COUNT_PROP_NAME, COUNT_PROP_LABEL } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -123,10 +123,12 @@ export class ESPewPewSource extends AbstractESAggSource { } createDefaultLayer(options) { + const defaultDynamicProperties = getDefaultDynamicProperties(); const styleDescriptor = VectorStyle.createDescriptor({ - [vectorStyles.LINE_COLOR]: { + [VECTOR_STYLES.LINE_COLOR]: { type: DynamicStyleProperty.type, options: { + ...defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR].options, field: { label: COUNT_PROP_LABEL, name: COUNT_PROP_NAME, @@ -135,9 +137,10 @@ export class ESPewPewSource extends AbstractESAggSource { color: 'Blues' } }, - [vectorStyles.LINE_WIDTH]: { + [VECTOR_STYLES.LINE_WIDTH]: { type: DynamicStyleProperty.type, options: { + ...defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options, field: { label: COUNT_PROP_LABEL, name: COUNT_PROP_NAME, @@ -203,13 +206,15 @@ export class ESPewPewSource extends AbstractESAggSource { } }); - const esResponse = await this._runEsQuery( - layerName, + const esResponse = await this._runEsQuery({ + requestId: this.getId(), + requestName: layerName, searchSource, registerCancelCallback, - i18n.translate('xpack.maps.source.pewPew.inspectorDescription', { + requestDescription: i18n.translate('xpack.maps.source.pewPew.inspectorDescription', { defaultMessage: 'Source-destination connections request' - })); + }), + }); const { featureCollection } = convertToLines(esResponse); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 57a43f924b7e6..453a1851e47aa 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -261,7 +261,13 @@ export class ESSearchSource extends AbstractESSource { } }); - const resp = await this._runEsQuery(layerName, searchSource, registerCancelCallback, 'Elasticsearch document top hits request'); + const resp = await this._runEsQuery({ + requestId: this.getId(), + requestName: layerName, + searchSource, + registerCancelCallback, + requestDescription: 'Elasticsearch document top hits request', + }); const allHits = []; const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []); @@ -322,7 +328,13 @@ export class ESSearchSource extends AbstractESSource { searchSource.setField('sort', this._buildEsSort()); } - const resp = await this._runEsQuery(layerName, searchSource, registerCancelCallback, 'Elasticsearch document request'); + const resp = await this._runEsQuery({ + requestId: this.getId(), + requestName: layerName, + searchSource, + registerCancelCallback, + requestDescription: 'Elasticsearch document request', + }); return { hits: resp.hits.hits.reverse(), // Reverse hits so top documents by sort are drawn on top diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index c2f4f7e755288..b5d7f7a6f606a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -54,7 +54,7 @@ export class AbstractESSource extends AbstractVectorSource { return []; } - supportsElasticsearchFilters() { + isESSource() { return true; } @@ -73,7 +73,7 @@ export class AbstractESSource extends AbstractVectorSource { return []; } - async _runEsQuery(requestName, searchSource, registerCancelCallback, requestDescription) { + async _runEsQuery({ requestId, requestName, requestDescription, searchSource, registerCancelCallback }) { const abortController = new AbortController(); registerCancelCallback(() => abortController.abort()); @@ -82,7 +82,7 @@ export class AbstractESSource extends AbstractVectorSource { inspectorAdapters: this._inspectorAdapters, searchSource, requestName, - requestId: this.getId(), + requestId, requestDesc: requestDescription, abortSignal: abortController.signal, }); @@ -271,4 +271,42 @@ export class AbstractESSource extends AbstractVectorSource { return fieldFromIndexPattern.format.getConverterFor('text'); } + async loadStylePropsMeta(layerName, style, dynamicStyleProps, registerCancelCallback, searchFilters) { + const promises = dynamicStyleProps.map(dynamicStyleProp => { + return dynamicStyleProp.getFieldMetaRequest(); + }); + + const fieldAggRequests = await Promise.all(promises); + const aggs = fieldAggRequests.reduce((aggs, fieldAggRequest) => { + return fieldAggRequest ? { ...aggs, ...fieldAggRequest } : aggs; + }, {}); + + const indexPattern = await this.getIndexPattern(); + const searchSource = new SearchSource(); + searchSource.setField('index', indexPattern); + searchSource.setField('size', 0); + searchSource.setField('aggs', aggs); + if (searchFilters.sourceQuery) { + searchSource.setField('query', searchFilters.sourceQuery); + } + if (style.isTimeAware() && await this.isTimeAware()) { + searchSource.setField('filter', [timefilter.createFilter(indexPattern, searchFilters.timeFilters)]); + } + + const resp = await this._runEsQuery({ + requestId: `${this.getId()}_styleMeta`, + requestName: i18n.translate('xpack.maps.source.esSource.stylePropsMetaRequestName', { + defaultMessage: '{layerName} - metadata', + values: { layerName } + }), + searchSource, + registerCancelCallback, + requestDescription: i18n.translate('xpack.maps.source.esSource.stylePropsMetaRequestDescription', { + defaultMessage: 'Elasticsearch request retrieving field metadata used for calculating symbolization bands.', + }), + }); + + return resp.aggregations; + } + } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index afc402fa81bcb..57366e502d581 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -103,9 +103,13 @@ export class ESTermSource extends AbstractESAggSource { const aggConfigs = new AggConfigs(indexPattern, configStates, aggSchemas.all); searchSource.setField('aggs', aggConfigs.toDsl()); - const requestName = `${this._descriptor.indexPatternTitle}.${this._termField.getName()}`; - const requestDesc = this._getRequestDescription(leftSourceName, leftFieldName); - const rawEsData = await this._runEsQuery(requestName, searchSource, registerCancelCallback, requestDesc); + const rawEsData = await this._runEsQuery({ + requestId: this.getId(), + requestName: `${this._descriptor.indexPatternTitle}.${this._termField.getName()}`, + searchSource, + registerCancelCallback, + requestDescription: this._getRequestDescription(leftSourceName, leftFieldName), + }); const metricPropertyNames = configStates .filter(configState => { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index 78e57f79bbe56..d3b2971dbbb0c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -123,7 +123,7 @@ export class AbstractSource { return AbstractSource.isIndexingSource; } - supportsElasticsearchFilters() { + isESSource() { return false; } @@ -136,6 +136,10 @@ export class AbstractSource { async getFieldFormatter(/* fieldName */) { return null; } + + async loadStylePropsMeta() { + throw new Error(`Source#loadStylePropsMeta not implemented`); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js index e4982c86b53bb..ed64f408b2585 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js @@ -10,13 +10,14 @@ import { AbstractStyle } from '../abstract_style'; import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants'; +import { LAYER_STYLE_TYPE } from '../../../../common/constants'; import { getColorRampStops } from '../color_utils'; import { i18n } from '@kbn/i18n'; import { EuiIcon } from '@elastic/eui'; export class HeatmapStyle extends AbstractStyle { - static type = 'HEATMAP'; + static type = LAYER_STYLE_TYPE.HEATMAP; constructor(descriptor = {}) { super(); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta_options_popover.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta_options_popover.js new file mode 100644 index 0000000000000..095740abe3dda --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta_options_popover.js @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { + EuiButtonIcon, + EuiFormRow, + EuiPopover, + EuiRange, + EuiSwitch, +} from '@elastic/eui'; +import { VECTOR_STYLES } from '../vector_style_defaults'; +import { i18n } from '@kbn/i18n'; + +function getIsEnableToggleLabel(styleName) { + switch (styleName) { + case VECTOR_STYLES.FILL_COLOR: + case VECTOR_STYLES.LINE_COLOR: + return i18n.translate('xpack.maps.styles.fieldMetaOptions.isEnabled.colorLabel', { + defaultMessage: 'Calculate color ramp range from indices' + }); + case VECTOR_STYLES.LINE_WIDTH: + return i18n.translate('xpack.maps.styles.fieldMetaOptions.isEnabled.widthLabel', { + defaultMessage: 'Calculate border width range from indices' + }); + case VECTOR_STYLES.ICON_SIZE: + return i18n.translate('xpack.maps.styles.fieldMetaOptions.isEnabled.sizeLabel', { + defaultMessage: 'Calculate symbol size range from indices' + }); + default: + return i18n.translate('xpack.maps.styles.fieldMetaOptions.isEnabled.defaultLabel', { + defaultMessage: 'Calculate symbolization range from indices' + }); + } +} + +export class FieldMetaOptionsPopover extends Component { + + state = { + isPopoverOpen: false, + }; + + _togglePopover = () => { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen, + }); + } + + _closePopover = () => { + this.setState({ + isPopoverOpen: false, + }); + } + + _onIsEnabledChange = event => { + this.props.onChange({ + ...this.props.styleProperty.getFieldMetaOptions(), + isEnabled: event.target.checked, + }); + }; + + _onSigmaChange = event => { + this.props.onChange({ + ...this.props.styleProperty.getFieldMetaOptions(), + sigma: event.target.value, + }); + } + + _renderButton() { + return ( + + ); + } + + _renderContent() { + return ( + + + + + + + + + + ); + } + + render() { + if (!this.props.styleProperty.supportsFieldMeta()) { + return null; + } + + return ( + + {this._renderContent()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js index 0984b0189558d..b21577d214bb5 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js @@ -6,27 +6,27 @@ import { i18n } from '@kbn/i18n'; -import { vectorStyles } from '../vector_style_defaults'; +import { VECTOR_STYLES } from '../vector_style_defaults'; export function getVectorStyleLabel(styleName) { switch (styleName) { - case vectorStyles.FILL_COLOR: + case VECTOR_STYLES.FILL_COLOR: return i18n.translate('xpack.maps.styles.vector.fillColorLabel', { defaultMessage: 'Fill color' }); - case vectorStyles.LINE_COLOR: + case VECTOR_STYLES.LINE_COLOR: return i18n.translate('xpack.maps.styles.vector.borderColorLabel', { defaultMessage: 'Border color' }); - case vectorStyles.LINE_WIDTH: + case VECTOR_STYLES.LINE_WIDTH: return i18n.translate('xpack.maps.styles.vector.borderWidthLabel', { defaultMessage: 'Border width' }); - case vectorStyles.ICON_SIZE: + case VECTOR_STYLES.ICON_SIZE: return i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', { defaultMessage: 'Symbol size' }); - case vectorStyles.ICON_ORIENTATION: + case VECTOR_STYLES.ICON_ORIENTATION: return i18n.translate('xpack.maps.styles.vector.orientationLabel', { defaultMessage: 'Symbol orientation' }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js index 35c7066b7fd0f..dc5098c4d6d4d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js @@ -81,18 +81,24 @@ export class StylePropertyLegendRow extends Component { } render() { - const { range, style } = this.props; if (this._excludeFromHeader()) { return null; } const header = style.renderHeader(); + + const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); + const minLabel = this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange ? `< ${min}` : min; + + const max = this._formatValue(_.get(range, 'max', EMPTY_VALUE)); + const maxLabel = this.props.style.isFieldMetaEnabled() && range && range.isMaxOutsideStdRange ? `> ${max}` : max; + return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js index d1de8e0fe6b4a..9686214fec9fe 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Component, Fragment } from 'react'; import { VectorStyle } from '../vector_style'; import { i18n } from '@kbn/i18n'; +import { FieldMetaOptionsPopover } from './field_meta_options_popover'; import { getVectorStyleLabel } from './get_vector_style_label'; import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiFormRow, EuiButtonToggle } from '@elastic/eui'; -export class StaticDynamicStyleRow extends React.Component { +export class StaticDynamicStyleRow extends Component { // Store previous options locally so when type is toggled, // previous style options can be used. prevStaticStyleOptions = this.props.defaultStaticStyleOptions; @@ -29,6 +30,17 @@ export class StaticDynamicStyleRow extends React.Component { return this.props.styleProperty.getOptions(); } + _onFieldMetaOptionsChange = fieldMetaOptions => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + ...this._getStyleOptions(), + fieldMetaOptions + } + }; + this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); + } + _onStaticStyleChange = options => { const styleDescriptor = { type: VectorStyle.STYLE_TYPE.STATIC, @@ -64,11 +76,17 @@ export class StaticDynamicStyleRow extends React.Component { if (this._isDynamic()) { const DynamicSelector = this.props.DynamicSelector; return ( - + + + + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 3043d57c04037..d848b9274d071 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -22,7 +22,7 @@ import { SYMBOLIZE_AS_ICON } from '../vector_constants'; import { i18n } from '@kbn/i18n'; import { SYMBOL_OPTIONS } from '../symbol_utils'; -import { EuiSpacer, EuiButtonGroup } from '@elastic/eui'; +import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; export class VectorStyleEditor extends Component { state = { @@ -117,6 +117,14 @@ export class VectorStyleEditor extends Component { return [...this.state.dateFields, ...this.state.numberFields]; } + _handleSelectedFeatureChange = selectedFeature => { + this.setState({ selectedFeature }); + }; + + _onIsTimeAwareChange = event => { + this.props.onIsTimeAwareChange(event.target.checked); + }; + _renderFillColor() { return ( { - this.setState({ selectedFeature }); - }; - - render() { + _renderProperties() { const { supportedFeatures, selectedFeature } = this.state; if (!supportedFeatures) { @@ -302,4 +306,34 @@ export class VectorStyleEditor extends Component { ); } + + _renderIsTimeAwareSwitch() { + if (!this.props.showIsTimeAware) { + return null; + } + + return ( + + + + ); + } + + render() { + return ( + + {this._renderProperties()} + {this._renderIsTimeAwareSwitch()} + + ); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js index 4b4b853c274cb..d56db31d17067 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js @@ -50,7 +50,7 @@ export class DynamicColorProperty extends DynamicStyleProperty { } isCustomColorRamp() { - return !!this._options.customColorRamp; + return this._options.useCustomColorRamp; } supportsFeatureState() { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js index fb4ffd8cce4b4..afbe924e1afb8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js @@ -7,14 +7,14 @@ import { DynamicStyleProperty } from './dynamic_style_property'; import { getComputedFieldName } from '../style_util'; -import { vectorStyles } from '../vector_style_defaults'; +import { VECTOR_STYLES } from '../vector_style_defaults'; export class DynamicOrientationProperty extends DynamicStyleProperty { syncIconRotationWithMb(symbolLayerId, mbMap) { if (this._options.field && this._options.field.name) { - const targetName = getComputedFieldName(vectorStyles.ICON_ORIENTATION, this._options.field.name); + const targetName = getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this._options.field.name); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', ['coalesce', ['get', targetName], 0]); } else { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index bd011b27d81c8..b4e6cf7be1701 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -8,7 +8,7 @@ import { DynamicStyleProperty } from './dynamic_style_property'; import { getComputedFieldName } from '../style_util'; import { HALF_LARGE_MAKI_ICON_SIZE, LARGE_MAKI_ICON_SIZE, SMALL_MAKI_ICON_SIZE } from '../symbol_utils'; -import { vectorStyles } from '../vector_style_defaults'; +import { VECTOR_STYLES } from '../vector_style_defaults'; import _ from 'lodash'; import { CircleIcon } from '../components/legend/circle_icon'; import React, { Fragment } from 'react'; @@ -55,7 +55,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); const halfIconPixels = iconPixels / 2; - const targetName = getComputedFieldName(vectorStyles.ICON_SIZE, this._options.field.name); + const targetName = getComputedFieldName(VECTOR_STYLES.ICON_SIZE, this._options.field.name); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ 'interpolate', @@ -112,9 +112,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty { renderHeader() { let icons; - if (this.getStyleName() === vectorStyles.LINE_WIDTH) { + if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { icons = getLineWidthIcons(); - } else if (this.getStyleName() === vectorStyles.ICON_SIZE) { + } else if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE) { icons = getSymbolSizeIcons(); } else { return null; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index e87bcc12c99be..a72502f9f17fb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ - +import _ from 'lodash'; import { AbstractStyleProperty } from './style_property'; +import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE } from '../../../../../common/constants'; export class DynamicStyleProperty extends AbstractStyleProperty { @@ -32,6 +33,22 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return this._field.getOrigin(); } + isFieldMetaEnabled() { + const fieldMetaOptions = this.getFieldMetaOptions(); + return this.supportsFieldMeta() && _.get(fieldMetaOptions, 'isEnabled', true); + } + + supportsFieldMeta() { + return this.isComplete() && this.isScaled() && this._field.supportsFieldMeta(); + } + + async getFieldMetaRequest() { + const fieldMetaOptions = this.getFieldMetaOptions(); + return this._field.getFieldMetaRequest({ + sigma: _.get(fieldMetaOptions, 'sigma', DEFAULT_SIGMA), + }); + } + supportsFeatureState() { return true; } @@ -39,4 +56,8 @@ export class DynamicStyleProperty extends AbstractStyleProperty { isScaled() { return true; } + + getFieldMetaOptions() { + return _.get(this.getOptions(), 'fieldMetaOptions', {}); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js index 69caaca080138..699955fe6542a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - export function getComputedFieldName(styleName, fieldName) { return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`; } @@ -12,3 +11,19 @@ export function getComputedFieldName(styleName, fieldName) { export function getComputedFieldNamePrefix(fieldName) { return `__kbn__dynamic__${fieldName}`; } + +export function scaleValue(value, range) { + if (isNaN(value) || !range) { + return -1; //Nothing to scale, put outside scaled range + } + + if (range.delta === 0 || value >= range.max) { + return 1; //snap to end of scaled range + } + + if (value <= range.min) { + return 0; //snap to beginning of scaled range + } + + return (value - range.min) / range.delta; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js new file mode 100644 index 0000000000000..a25e3bf8684c9 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -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 { scaleValue } from './style_util'; + +describe('scaleValue', () => { + test('Should scale value between 0 and 1', () => { + expect(scaleValue(5, { min: 0, max: 10, delta: 10 })).toBe(0.5); + }); + + test('Should snap value less then range min to 0', () => { + expect(scaleValue(-1, { min: 0, max: 10, delta: 10 })).toBe(0); + }); + + test('Should snap value greater then range max to 1', () => { + expect(scaleValue(11, { min: 0, max: 10, delta: 10 })).toBe(1); + }); + + test('Should snap value to 1 when tere is not range delta', () => { + expect(scaleValue(10, { min: 10, max: 10, delta: 0 })).toBe(1); + }); + + test('Should put value as -1 when value is not provided', () => { + expect(scaleValue(undefined, { min: 0, max: 10, delta: 10 })).toBe(-1); + }); + + test('Should put value as -1 when range is not provided', () => { + expect(scaleValue(5, undefined)).toBe(-1); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 45a1636e5c033..53794f2043aad 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -7,15 +7,21 @@ import _ from 'lodash'; import React from 'react'; import { VectorStyleEditor } from './components/vector_style_editor'; -import { getDefaultProperties, vectorStyles } from './vector_style_defaults'; +import { getDefaultProperties, VECTOR_STYLES } from './vector_style_defaults'; import { AbstractStyle } from '../abstract_style'; -import { GEO_JSON_TYPE, FIELD_ORIGIN, STYLE_TYPE } from '../../../../common/constants'; +import { + GEO_JSON_TYPE, + FIELD_ORIGIN, + STYLE_TYPE, + SOURCE_META_ID_ORIGIN, + LAYER_STYLE_TYPE, +} from '../../../../common/constants'; import { VectorIcon } from './components/legend/vector_icon'; import { VectorStyleLegend } from './components/legend/vector_style_legend'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants'; import { getMakiSymbolAnchor } from './symbol_utils'; -import { getComputedFieldName } from './style_util'; +import { getComputedFieldName, scaleValue } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; import { DynamicSizeProperty } from './properties/dynamic_size_property'; @@ -31,12 +37,13 @@ const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON]; export class VectorStyle extends AbstractStyle { - static type = 'VECTOR'; + static type = LAYER_STYLE_TYPE.VECTOR; static STYLE_TYPE = STYLE_TYPE; - static createDescriptor(properties = {}) { + static createDescriptor(properties = {}, isTimeAware = true) { return { type: VectorStyle.type, - properties: { ...getDefaultProperties(), ...properties } + properties: { ...getDefaultProperties(), ...properties }, + isTimeAware, }; } @@ -50,15 +57,15 @@ export class VectorStyle extends AbstractStyle { this._layer = layer; this._descriptor = { ...descriptor, - ...VectorStyle.createDescriptor(descriptor.properties), + ...VectorStyle.createDescriptor(descriptor.properties, descriptor.isTimeAware), }; - this._lineColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.LINE_COLOR], vectorStyles.LINE_COLOR); - this._fillColorStyleProperty = this._makeColorProperty(this._descriptor.properties[vectorStyles.FILL_COLOR], vectorStyles.FILL_COLOR); - this._lineWidthStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.LINE_WIDTH], vectorStyles.LINE_WIDTH); - this._iconSizeStyleProperty = this._makeSizeProperty(this._descriptor.properties[vectorStyles.ICON_SIZE], vectorStyles.ICON_SIZE); + this._lineColorStyleProperty = this._makeColorProperty(this._descriptor.properties[VECTOR_STYLES.LINE_COLOR], VECTOR_STYLES.LINE_COLOR); + this._fillColorStyleProperty = this._makeColorProperty(this._descriptor.properties[VECTOR_STYLES.FILL_COLOR], VECTOR_STYLES.FILL_COLOR); + this._lineWidthStyleProperty = this._makeSizeProperty(this._descriptor.properties[VECTOR_STYLES.LINE_WIDTH], VECTOR_STYLES.LINE_WIDTH); + this._iconSizeStyleProperty = this._makeSizeProperty(this._descriptor.properties[VECTOR_STYLES.ICON_SIZE], VECTOR_STYLES.ICON_SIZE); // eslint-disable-next-line max-len - this._iconOrientationProperty = this._makeOrientationProperty(this._descriptor.properties[vectorStyles.ICON_ORIENTATION], vectorStyles.ICON_ORIENTATION); + this._iconOrientationProperty = this._makeOrientationProperty(this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION], VECTOR_STYLES.ICON_ORIENTATION); } _getAllStyleProperties() { @@ -72,13 +79,22 @@ export class VectorStyle extends AbstractStyle { } renderEditor({ layer, onStyleDescriptorChange }) { - const styleProperties = { ...this.getRawProperties() }; + const rawProperties = this.getRawProperties(); const handlePropertyChange = (propertyName, settings) => { - styleProperties[propertyName] = settings;//override single property, but preserve the rest - const vectorStyleDescriptor = VectorStyle.createDescriptor(styleProperties); + rawProperties[propertyName] = settings;//override single property, but preserve the rest + const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, this.isTimeAware()); onStyleDescriptorChange(vectorStyleDescriptor); }; + const onIsTimeAwareChange = isTimeAware => { + const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, isTimeAware); + onStyleDescriptorChange(vectorStyleDescriptor); + }; + + const propertiesWithFieldMeta = this.getDynamicPropertiesArray().filter(dynamicStyleProp => { + return dynamicStyleProp.isFieldMetaEnabled(); + }); + return ( 0} /> ); } @@ -156,7 +175,7 @@ export class VectorStyle extends AbstractStyle { nextStyleDescriptor: VectorStyle.createDescriptor({ ...originalProperties, ...updatedProperties, - }) + }, this.isTimeAware()) }; } @@ -239,6 +258,10 @@ export class VectorStyle extends AbstractStyle { return fieldNames; } + isTimeAware() { + return this._descriptor.isTimeAware; + } + getRawProperties() { return this._descriptor.properties || {}; } @@ -277,7 +300,56 @@ export class VectorStyle extends AbstractStyle { } _getFieldRange = (fieldName) => { - return _.get(this._descriptor, ['__styleMeta', fieldName]); + const fieldRangeFromLocalFeatures = _.get(this._descriptor, ['__styleMeta', fieldName]); + const dynamicProps = this.getDynamicPropertiesArray(); + const dynamicProp = dynamicProps.find(dynamicProp => { return fieldName === dynamicProp.getField().getName(); }); + + if (!dynamicProp || !dynamicProp.isFieldMetaEnabled()) { + return fieldRangeFromLocalFeatures; + } + + let dataRequestId; + if (dynamicProp.getFieldOrigin() === FIELD_ORIGIN.SOURCE) { + dataRequestId = SOURCE_META_ID_ORIGIN; + } else { + const join = this._layer.getValidJoins().find(join => { + const matchingField = join.getRightJoinSource().getMetricFieldForName(fieldName); + return !!matchingField; + }); + if (join) { + dataRequestId = join.getSourceMetaDataRequestId(); + } + } + + if (!dataRequestId) { + return fieldRangeFromLocalFeatures; + } + + const styleMetaDataRequest = this._layer._findDataRequestForSource(dataRequestId); + if (!styleMetaDataRequest || !styleMetaDataRequest.hasData()) { + return fieldRangeFromLocalFeatures; + } + + const data = styleMetaDataRequest.getData(); + const field = dynamicProp.getField(); + const realFieldName = field.getESDocFieldName ? field.getESDocFieldName() : field.getName(); + const stats = data[realFieldName]; + if (!stats) { + return fieldRangeFromLocalFeatures; + } + + const sigma = _.get(dynamicProp.getFieldMetaOptions(), 'sigma', 3); + const stdLowerBounds = stats.avg - (stats.std_deviation * sigma); + const stdUpperBounds = stats.avg + (stats.std_deviation * sigma); + const min = Math.max(stats.min, stdLowerBounds); + const max = Math.min(stats.max, stdUpperBounds); + return { + min, + max, + delta: max - min, + isMinOutsideStdRange: stats.min < stdLowerBounds, + isMaxOutsideStdRange: stats.max > stdUpperBounds, + }; } getIcon = () => { @@ -289,8 +361,8 @@ export class VectorStyle extends AbstractStyle { ); @@ -321,7 +393,7 @@ export class VectorStyle extends AbstractStyle { // To work around this limitation, some styling values must fall back to geojson property values. let supportsFeatureState; let isScaled; - if (styleProperty.getStyleName() === vectorStyles.ICON_SIZE + if (styleProperty.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON) { supportsFeatureState = false; isScaled = true; @@ -380,13 +452,7 @@ export class VectorStyle extends AbstractStyle { const value = parseFloat(feature.properties[name]); let styleValue; if (isScaled) { - if (isNaN(value) || !range) {//cannot scale - styleValue = -1;//put outside range - } else if (range.delta === 0) {//values are identical - styleValue = 1;//snap to end of color range - } else { - styleValue = (value - range.min) / range.delta; - } + styleValue = scaleValue(value, range); } else { if (isNaN(value)) { styleValue = 0; @@ -450,7 +516,6 @@ export class VectorStyle extends AbstractStyle { } _makeField(fieldDescriptor) { - if (!fieldDescriptor || !fieldDescriptor.name) { return null; } @@ -473,8 +538,6 @@ export class VectorStyle extends AbstractStyle { } else { throw new Error(`Unknown origin-type ${fieldDescriptor.origin}`); } - - } _makeSizeProperty(descriptor, styleName) { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index ea4228430d13d..b834fb842389e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -16,8 +16,9 @@ const DEFAULT_ICON = 'airfield'; export const DEFAULT_MIN_SIZE = 1; export const DEFAULT_MAX_SIZE = 64; +export const DEFAULT_SIGMA = 3; -export const vectorStyles = { +export const VECTOR_STYLES = { SYMBOL: 'symbol', FILL_COLOR: 'fillColor', LINE_COLOR: 'lineColor', @@ -29,7 +30,7 @@ export const vectorStyles = { export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), - [vectorStyles.SYMBOL]: { + [VECTOR_STYLES.SYMBOL]: { options: { symbolizeAs: SYMBOLIZE_AS_CIRCLE, symbolId: DEFAULT_ICON, @@ -48,31 +49,31 @@ export function getDefaultStaticProperties(mapColors = []) { return { - [vectorStyles.FILL_COLOR]: { + [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { color: nextFillColor, } }, - [vectorStyles.LINE_COLOR]: { + [VECTOR_STYLES.LINE_COLOR]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { color: nextLineColor } }, - [vectorStyles.LINE_WIDTH]: { + [VECTOR_STYLES.LINE_WIDTH]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { size: 1 } }, - [vectorStyles.ICON_SIZE]: { + [VECTOR_STYLES.ICON_SIZE]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { size: DEFAULT_ICON_SIZE } }, - [vectorStyles.ICON_ORIENTATION]: { + [VECTOR_STYLES.ICON_ORIENTATION]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { orientation: 0 @@ -83,40 +84,60 @@ export function getDefaultStaticProperties(mapColors = []) { export function getDefaultDynamicProperties() { return { - [vectorStyles.FILL_COLOR]: { + [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { color: COLOR_GRADIENTS[0].value, field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + } } }, - [vectorStyles.LINE_COLOR]: { + [VECTOR_STYLES.LINE_COLOR]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { color: COLOR_GRADIENTS[0].value, field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + } } }, - [vectorStyles.LINE_WIDTH]: { + [VECTOR_STYLES.LINE_WIDTH]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { minSize: DEFAULT_MIN_SIZE, maxSize: DEFAULT_MAX_SIZE, field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + } } }, - [vectorStyles.ICON_SIZE]: { + [VECTOR_STYLES.ICON_SIZE]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { minSize: DEFAULT_MIN_SIZE, maxSize: DEFAULT_MAX_SIZE, field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + } } }, - [vectorStyles.ICON_ORIENTATION]: { + [VECTOR_STYLES.ICON_ORIENTATION]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + } } }, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js index 610c704b34ec6..557a2bf869987 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js @@ -128,3 +128,22 @@ export async function canSkipSourceUpdate({ source, prevDataRequest, nextMeta }) && !updateDueToPrecisionChange && !updateDueToSourceMetaChange; } + +export function canSkipStyleMetaUpdate({ prevDataRequest, nextMeta }) { + if (!prevDataRequest) { + return false; + } + const prevMeta = prevDataRequest.getMeta(); + if (!prevMeta) { + return false; + } + + const updateDueToFields = !_.isEqual(prevMeta.dynamicStyleFields, nextMeta.dynamicStyleFields); + + const updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery); + + const updateDueToIsTimeAware = nextMeta.isTimeAware !== prevMeta.isTimeAware; + const updateDueToTime = nextMeta.isTimeAware ? !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters) : false; + + return !updateDueToFields && !updateDueToSourceQuery && !updateDueToIsTimeAware && !updateDueToTime; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js index 77359a6def48f..24728f2ac95fd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js @@ -126,7 +126,8 @@ describe('canSkipSourceUpdate', () => { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, - } + }, + data: {} }); it('can skip update when filter changes', async () => { @@ -210,7 +211,8 @@ describe('canSkipSourceUpdate', () => { applyGlobalQuery: prevApplyGlobalQuery, filters: prevFilters, query: prevQuery, - } + }, + data: {} }); it('can not skip update when filter changes', async () => { diff --git a/x-pack/legacy/plugins/maps/public/layers/util/data_request.js b/x-pack/legacy/plugins/maps/public/layers/util/data_request.js index 95b82aa292884..12d57afbe1c87 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/data_request.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/data_request.js @@ -22,7 +22,7 @@ export class DataRequest { } getMeta() { - return _.get(this._descriptor, 'dataMeta', {}); + return this.hasData() ? _.get(this._descriptor, 'dataMeta', {}) : _.get(this._descriptor, 'dataMetaAtStart', {}); } hasData() { diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js b/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js similarity index 56% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js rename to x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js index 8e66ff4891643..54d8794b1e3cf 100644 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { putLicense } from '../../../lib/license'; +import { METRIC_TYPE } from '../../../common/constants'; -export function registerLicenseRoute(router, xpackInfo) { - router.put('', (request) => { - return putLicense(request, xpackInfo); - }); +export function isMetricCountable(aggType) { + return [METRIC_TYPE.COUNT, METRIC_TYPE.SUM, METRIC_TYPE.UNIQUE_COUNT].includes(aggType); } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 57126bb7681b8..7e831115e6dba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -12,16 +12,19 @@ import { InnerJoin } from './joins/inner_join'; import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN, + SOURCE_META_ID_ORIGIN, FEATURE_VISIBLE_PROPERTY_NAME, EMPTY_FEATURE_COLLECTION, - LAYER_TYPE + LAYER_TYPE, + FIELD_ORIGIN, + LAYER_STYLE_TYPE, } from '../../common/constants'; import _ from 'lodash'; import { JoinTooltipProperty } from './tooltips/join_tooltip_property'; import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataRequestAbortError } from './util/data_request'; -import { canSkipSourceUpdate } from './util/can_skip_fetch'; +import { canSkipSourceUpdate, canSkipStyleMetaUpdate } from './util/can_skip_fetch'; import { assignFeatureIds } from './util/assign_feature_ids'; import { getFillFilterExpression, @@ -88,7 +91,7 @@ export class VectorLayer extends AbstractLayer { const joins = this.getValidJoins(); for (let i = 0; i < joins.length; i++) { - const joinDataRequest = this.getDataRequest(joins[i].getSourceId()); + const joinDataRequest = this.getDataRequest(joins[i].getSourceDataRequestId()); if (!joinDataRequest || !joinDataRequest.hasData()) { return false; } @@ -229,12 +232,10 @@ export class VectorLayer extends AbstractLayer { return this._dataRequests.find(dataRequest => dataRequest.getDataId() === sourceDataId); } - - async _syncJoin({ join, startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters }) { const joinSource = join.getRightJoinSource(); - const sourceDataId = join.getSourceId(); + const sourceDataId = join.getSourceDataRequestId(); const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`); const searchFilters = { ...dataFilters, @@ -287,6 +288,7 @@ export class VectorLayer extends AbstractLayer { async _syncJoins(syncContext) { const joinSyncs = this.getValidJoins().map(async join => { + await this._syncJoinStyleMeta(syncContext, join); return this._syncJoin({ join, ...syncContext }); }); @@ -350,7 +352,7 @@ export class VectorLayer extends AbstractLayer { startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters }) { - const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`); + const requestToken = Symbol(`layer-source-data:${this.getId()}`); const searchFilters = this._getSearchFilters(dataFilters); const prevDataRequest = this.getSourceDataRequest(); @@ -389,11 +391,89 @@ export class VectorLayer extends AbstractLayer { } } + async _syncSourceStyleMeta(syncContext) { + if (this._style.constructor.type !== LAYER_STYLE_TYPE.VECTOR) { + return; + } + + return this._syncStyleMeta({ + source: this._source, + sourceQuery: this.getQuery(), + dataRequestId: SOURCE_META_ID_ORIGIN, + dynamicStyleProps: this._style.getDynamicPropertiesArray().filter(dynamicStyleProp => { + return dynamicStyleProp.getFieldOrigin() === FIELD_ORIGIN.SOURCE && dynamicStyleProp.isFieldMetaEnabled(); + }), + ...syncContext + }); + } + + async _syncJoinStyleMeta(syncContext, join) { + const joinSource = join.getRightJoinSource(); + return this._syncStyleMeta({ + source: joinSource, + sourceQuery: joinSource.getWhereQuery(), + dataRequestId: join.getSourceMetaDataRequestId(), + dynamicStyleProps: this._style.getDynamicPropertiesArray().filter(dynamicStyleProp => { + const matchingField = joinSource.getMetricFieldForName(dynamicStyleProp.getField().getName()); + return dynamicStyleProp.getFieldOrigin() === FIELD_ORIGIN.JOIN + && !!matchingField + && dynamicStyleProp.isFieldMetaEnabled(); + }), + ...syncContext + }); + } + + async _syncStyleMeta({ + source, + sourceQuery, + dataRequestId, + dynamicStyleProps, + dataFilters, + startLoading, + stopLoading, + onLoadError, + registerCancelCallback + }) { + + if (!source.isESSource() || dynamicStyleProps.length === 0) { + return; + } + + const dynamicStyleFields = dynamicStyleProps.map(dynamicStyleProp => { + return dynamicStyleProp.getField().getName(); + }); + + const nextMeta = { + dynamicStyleFields: _.uniq(dynamicStyleFields).sort(), + sourceQuery, + isTimeAware: this._style.isTimeAware() && await source.isTimeAware(), + timeFilters: dataFilters.timeFilters, + }; + const prevDataRequest = this._findDataRequestForSource(dataRequestId); + const canSkipFetch = canSkipStyleMetaUpdate({ prevDataRequest, nextMeta }); + if (canSkipFetch) { + return; + } + + const requestToken = Symbol(`layer-${this.getId()}-style-meta`); + try { + startLoading(dataRequestId, requestToken, nextMeta); + const layerName = await this.getDisplayName(); + const styleMeta = await source.loadStylePropsMeta(layerName, this._style, dynamicStyleProps, registerCancelCallback, nextMeta); + stopLoading(dataRequestId, requestToken, styleMeta, nextMeta); + } catch (error) { + if (!(error instanceof DataRequestAbortError)) { + onLoadError(dataRequestId, requestToken, error.message); + } + } + } + async syncData(syncContext) { if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) { return; } + await this._syncSourceStyleMeta(syncContext); const sourceResult = await this._syncSource(syncContext); if ( !sourceResult.featureCollection || diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts index 778872d8183ee..777327c639ebc 100644 --- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts +++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatterns } from 'ui/index_patterns'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; export const indexPatternsMock = (new (class { fieldFormats = []; @@ -18,4 +18,4 @@ export const indexPatternsMock = (new (class { getIds = jest.fn(); getTitles = jest.fn(); make = jest.fn(); -})() as unknown) as IndexPatterns; +})() as unknown) as IndexPatternsContract; diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts index aed230a53f62a..00989245e20e7 100644 --- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -9,7 +9,10 @@ import React from 'react'; import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types'; -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; +import { + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../../src/plugins/data/public'; // set() method is missing in original d.ts export interface KibanaConfigTypeFix extends KibanaConfig { @@ -20,7 +23,7 @@ export interface KibanaContextValue { combinedQuery: any; currentIndexPattern: IndexPattern; currentSavedSearch: SavedSearch; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; kibanaConfig: KibanaConfigTypeFix; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx index c41285f40d64b..1d4ac85ae2e87 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx @@ -11,8 +11,8 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; import { InjectorService } from '../../../../../common/types/angular'; import { createSearchItems } from '../../../jobs/new_job/utils/new_job_utils'; @@ -29,7 +29,7 @@ module.directive('mlDataFrameAnalyticsExploration', ($injector: InjectorService) const globalState = $injector.get('globalState'); globalState.fetch(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx index 8299ff53393bb..5d97ed6dfcd3d 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx @@ -11,10 +11,10 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../common/types/angular'; import { createSearchItems } from '../../../jobs/new_job/utils/new_job_utils'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; import { KibanaConfigTypeFix, KibanaContext } from '../../../contexts/kibana'; @@ -25,7 +25,7 @@ module.directive('mlDataFrameAnalyticsManagement', ($injector: InjectorService) scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 3776245d90c81..99e61d5937c1d 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -5,8 +5,8 @@ */ import React, { FC, Fragment } from 'react'; -import { IndexPatterns } from 'ui/index_patterns'; import { timefilter } from 'ui/timefilter'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; import { KibanaConfigTypeFix } from '../../contexts/kibana'; import { NavigationMenu } from '../../components/navigation_menu'; @@ -15,7 +15,7 @@ import { NavigationMenu } from '../../components/navigation_menu'; import { FileDataVisualizerView } from './components/file_datavisualizer_view/index'; export interface FileDataVisualizerPageProps { - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; kibanaConfig: KibanaConfigTypeFix; } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx index 291e03a96e85f..7ca2db041da29 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx @@ -14,7 +14,6 @@ import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import uiRoutes from 'ui/routes'; -import { IndexPatterns } from 'ui/index_patterns'; import { KibanaConfigTypeFix } from '../../contexts/kibana'; import { getFileDataVisualizerBreadcrumbs } from './breadcrumbs'; import { InjectorService } from '../../../../common/types/angular'; @@ -24,6 +23,7 @@ import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes'; import { loadMlServerInfo } from '../../services/ml_server_info'; import { loadIndexPatterns } from '../../util/index_utils'; import { FileDataVisualizerPage, FileDataVisualizerPageProps } from './file_datavisualizer'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; const template = `
@@ -47,7 +47,7 @@ module.directive('fileDatavisualizerPage', function($injector: InjectorService) scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const props: FileDataVisualizerPageProps = { diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx index 58cd1c2c6fd0c..5de7cb6b71acb 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx @@ -12,7 +12,7 @@ import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { I18nContext } from 'ui/i18n'; -import { IndexPatterns } from 'ui/index_patterns'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; import { InjectorService } from '../../../../common/types/angular'; import { KibanaConfigTypeFix, KibanaContext } from '../../contexts/kibana/kibana_context'; @@ -25,7 +25,7 @@ module.directive('mlDataVisualizer', ($injector: InjectorService) => { scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx index 8d54ca65a2852..3f2a7e553c7e0 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx @@ -11,7 +11,6 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../../common/types/angular'; @@ -19,6 +18,7 @@ import { createSearchItems } from '../../utils/new_job_utils'; import { Page } from './page'; import { KibanaContext, KibanaConfigTypeFix } from '../../../../contexts/kibana'; +import { IndexPatternsContract } from '../../../../../../../../../../src/plugins/data/public'; module.directive('mlJobTypePage', ($injector: InjectorService) => { return { @@ -29,7 +29,7 @@ module.directive('mlJobTypePage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx index db4078ba1bbc8..d152dfc488ff8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx @@ -11,9 +11,9 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; - import { I18nContext } from 'ui/i18n'; +import { IndexPatternsContract } from '../../../../../../../../../../src/plugins/data/public'; + import { InjectorService } from '../../../../../../common/types/angular'; import { createSearchItems } from '../../utils/new_job_utils'; import { Page, PageProps } from './page'; @@ -29,7 +29,7 @@ module.directive('mlNewJobPage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); const existingJobsAndGroups = $route.current.locals.existingJobsAndGroups; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx index 2d08a1da07459..4ed12dfff4c20 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx @@ -11,7 +11,6 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../common/types/angular'; @@ -20,6 +19,7 @@ import { createSearchItems } from '../utils/new_job_utils'; import { Page } from './page'; import { KibanaContext, KibanaConfigTypeFix } from '../../../contexts/kibana'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; module.directive('mlRecognizePage', ($injector: InjectorService) => { return { @@ -30,7 +30,7 @@ module.directive('mlRecognizePage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx index 156e53b19874f..ff81f0e87aca8 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx @@ -26,6 +26,8 @@ import { import { AnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; +const MlInMemoryTable = mlInMemoryTableFactory(); + interface Props { items: DataFrameAnalyticsListRow[]; } @@ -113,8 +115,6 @@ export const AnalyticsTable: FC = ({ items }) => { }, }; - const MlInMemoryTable = mlInMemoryTableFactory(); - return ( (); + // Used to pass on attribute names to table columns export enum AnomalyDetectionListColumns { id = 'id', @@ -195,8 +197,6 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }, }; - const MlInMemoryTable = mlInMemoryTableFactory(); - return ( diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts index aeec71462308e..f79515c80556a 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { SavedSearchLoader } from 'src/legacy/core_plugins/kibana/public/discover/types'; import { @@ -15,13 +14,17 @@ import { NewJobCaps, EVENT_RATE_FIELD_ID, } from '../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { + ES_FIELD_TYPES, + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../src/plugins/data/public'; import { ml } from './ml_api_service'; // called in the angular routing resolve block to initialize the // newJobCapsService with the currently selected index pattern export function loadNewJobCapabilities( - indexPatterns: IndexPatterns, + indexPatterns: IndexPatternsContract, savedSearches: SavedSearchLoader, $route: Record ) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts index f25821e8ca1ca..99882b0243be8 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts @@ -6,19 +6,19 @@ import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { SavedObjectAttributes, SimpleSavedObject } from 'kibana/public'; import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; import { SavedSearchLoader } from '../../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; -import { start as data } from '../../../../../../../src/legacy/core_plugins/data/public/legacy'; +import { IndexPattern, IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; type IndexPatternSavedObject = SimpleSavedObject; let indexPatternCache: IndexPatternSavedObject[] = []; -let fullIndexPatterns: IndexPatterns | null = null; +let fullIndexPatterns: IndexPatternsContract | null = null; export function loadIndexPatterns() { - fullIndexPatterns = data.indexPatterns.indexPatterns; + fullIndexPatterns = npStart.plugins.data.indexPatterns; const savedObjectsClient = chrome.getSavedObjectsClient(); return savedObjectsClient .find({ @@ -49,7 +49,10 @@ export function getIndexPatternIdFromName(name: string) { return null; } -export function loadCurrentIndexPattern(indexPatterns: IndexPatterns, $route: Record) { +export function loadCurrentIndexPattern( + indexPatterns: IndexPatternsContract, + $route: Record +) { fullIndexPatterns = indexPatterns; return fullIndexPatterns.get($route.current.params.index); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 24e304b0010d0..1d93b9251bbd6 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -9,11 +9,11 @@ import routes from 'ui/routes'; import { capabilities } from 'ui/capabilities'; import { kfetch } from 'ui/kfetch'; import { fatalError, toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; import template from 'plugins/security/views/management/edit_role/edit_role.html'; import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; -import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { SpacesManager } from '../../../../../spaces/public/lib'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; @@ -75,8 +75,7 @@ const routeDefinition = (action) => ({ .then(users => _.map(users, 'username')); }, indexPatterns() { - const { indexPatterns } = data.indexPatterns; - return indexPatterns.getTitles(); + return npStart.plugins.data.indexPatterns.getTitles(); }, spaces(spacesEnabled) { if (spacesEnabled) { diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 11b97738fcf52..0924b6c6eb5e6 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -51,3 +51,5 @@ export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; * Default signals index key for kibana.dev.yml */ export const SIGNALS_INDEX_KEY = 'signalsIndex'; +export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals`; +export const DETECTION_ENGINE_SIGNALS_STATUS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/status`; diff --git a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx index 3548fb7c0e671..be449e3d422d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx @@ -6,7 +6,6 @@ import { EuiBadge } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -39,7 +38,7 @@ export type AndOr = 'and' | 'or'; /** Displays AND / OR in a round badge */ // Ref: https://github.com/elastic/eui/issues/1655 -export const AndOrBadge = pure<{ type: AndOr }>(({ type }) => { +export const AndOrBadge = React.memo<{ type: AndOr }>(({ type }) => { return ( {type === 'and' ? i18n.AND : i18n.OR} diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/arrows/__snapshots__/index.test.tsx.snap index 408bcac756f47..7702695520790 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/arrows/__snapshots__/index.test.tsx.snap @@ -1,9 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`arrows ArrowBody renders correctly against snapshot 1`] = ` - - + - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx index 8be0e7c267ec0..10d3c899562e8 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; @@ -15,12 +15,12 @@ import { ArrowBody, ArrowHead } from '.'; describe('arrows', () => { describe('ArrowBody', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow( + const wrapper = mount( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('ArrowBody'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx index 6d5b464e0e886..dfc7645c564d2 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx @@ -6,7 +6,6 @@ import { EuiIcon } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; /** Renders the body (non-pointy part) of an arrow */ @@ -21,7 +20,7 @@ ArrowBody.displayName = 'ArrowBody'; export type ArrowDirection = 'arrowLeft' | 'arrowRight'; /** Renders the head of an arrow */ -export const ArrowHead = pure<{ +export const ArrowHead = React.memo<{ direction: ArrowDirection; }>(({ direction }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx index 37ec256ccd8c0..f8db7d754aab1 100644 --- a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx @@ -6,7 +6,6 @@ import { EuiText } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; @@ -36,7 +35,7 @@ FingerprintLabel.displayName = 'FingerprintLabel'; * 'tls.client_certificate.fingerprint.sha1' * 'tls.server_certificate.fingerprint.sha1' */ -export const CertificateFingerprint = pure<{ +export const CertificateFingerprint = React.memo<{ eventId: string; certificateType: CertificateType; contextId: string; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index 7218d7a497f19..99ad995e48852 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; - import { Chart, BarSeries, @@ -63,6 +62,7 @@ export const BarChartBaseComponent = ({ ...chartDefaultSettings, ...get('configs.settings', chartConfigs), }; + return chartConfigs.width && chartConfigs.height ? ( @@ -116,6 +116,7 @@ export const BarChartComponent = ({ }) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); + return checkIfAnyValidSeriesExist(barChart) ? ( {({ measureRef, content: { height, width } }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap index 03a04983f9f86..f082dc4023e7a 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar.test.tsx.snap @@ -1,32 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UtilityBar it renders 1`] = ` - - - - - - Test text - - - - - Test action - - - - - - - Test action - - - - - + + + + + Test text + + + + + Test action + + + + + + + Test action + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap index 470b40cd1d960..eb20ac217b300 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap @@ -1,11 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UtilityBarAction it renders 1`] = ` - - - Test action - - + + Test action + `; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap index 62ff1b17dd55f..8ef7ee1cfe842 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap @@ -1,11 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UtilityBarGroup it renders 1`] = ` - - - - Test text - - - + + + Test text + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap index f81717c892755..2fe3b8ac5c7aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap @@ -1,13 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UtilityBarSection it renders 1`] = ` - - - - - Test text - - - - + + + + Test text + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap index 446b5556945d8..cf635ffa49c4c 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap @@ -1,9 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UtilityBarText it renders 1`] = ` - - - Test text - - + + Test text + `; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx index 27688ec24530e..68522377bd847 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx @@ -47,7 +47,7 @@ describe('UtilityBar', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('UtilityBar'))).toMatchSnapshot(); }); test('it applies border styles when border is true', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx index f226e0e055391..524769361ea9d 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.tsx @@ -8,11 +8,12 @@ import React from 'react'; import { Bar, BarProps } from './styles'; -export interface UtilityBarProps extends BarProps { +interface UtilityBarProps extends BarProps { children: React.ReactNode; } export const UtilityBar = React.memo(({ border, children }) => ( {children} )); + UtilityBar.displayName = 'UtilityBar'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx index f71bdfda705d0..7921c1ef42200 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx @@ -22,7 +22,7 @@ describe('UtilityBarAction', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('UtilityBarAction'))).toMatchSnapshot(); }); test('it renders a popover', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx index 2ad48bc9b9c92..f695c33a37447 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.tsx @@ -37,6 +37,7 @@ const Popover = React.memo( ); } ); + Popover.displayName = 'Popover'; export interface UtilityBarActionProps extends LinkIconProps { @@ -71,4 +72,5 @@ export const UtilityBarAction = React.memo( ) ); + UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx index 84ad96c5a1e5e..294d27fa95b3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx @@ -24,6 +24,6 @@ describe('UtilityBarGroup', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('UtilityBarGroup'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx index 1e23fd3498199..723035df672a9 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.tsx @@ -15,4 +15,5 @@ export interface UtilityBarGroupProps { export const UtilityBarGroup = React.memo(({ children }) => ( {children} )); + UtilityBarGroup.displayName = 'UtilityBarGroup'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx index 2dfc1d3b8d193..e0e0acc3a71c9 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx @@ -26,6 +26,6 @@ describe('UtilityBarSection', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('UtilityBarSection'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx index c457e6bc3dee0..42532c0355607 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.tsx @@ -15,4 +15,5 @@ export interface UtilityBarSectionProps { export const UtilityBarSection = React.memo(({ children }) => ( {children} )); + UtilityBarSection.displayName = 'UtilityBarSection'; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx index 0743e5cab02b4..29e1844bb2d4f 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx @@ -22,6 +22,6 @@ describe('UtilityBarText', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('UtilityBarText'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx index f8eb25f03d4ad..6195e008dbe27 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.tsx @@ -15,4 +15,5 @@ export interface UtilityBarTextProps { export const UtilityBarText = React.memo(({ children }) => ( {children} )); + UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx index b5d6fcfc6cef7..9295e055f918d 100644 --- a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { NetworkDirectionEcs } from '../../graphql/types'; import { DraggableBadge } from '../draggables'; @@ -56,7 +55,7 @@ export const getDirectionIcon = ( /** * Renders a badge containing the value of `network.direction` */ -export const DirectionBadge = pure<{ +export const DirectionBadge = React.memo<{ contextId: string; direction?: string | null; eventId: string; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap index 22c7b62711795..666a8249c27d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap @@ -1,426 +1,419 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = ` - - - - Drag drop context wrapper children - - - + "client.bytes": Object { + "aggregatable": true, + "category": "client", + "description": "Bytes sent from the client to the server.", + "example": "184", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "client.bytes", + "searchable": true, + "type": "number", + }, + "client.domain": Object { + "aggregatable": true, + "category": "client", + "description": "Client domain.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "client.domain", + "searchable": true, + "type": "string", + }, + "client.geo.country_iso_code": Object { + "aggregatable": true, + "category": "client", + "description": "Country ISO code.", + "example": "CA", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "client.geo.country_iso_code", + "searchable": true, + "type": "string", + }, + }, + }, + "cloud": Object { + "fields": Object { + "cloud.account.id": Object { + "aggregatable": true, + "category": "cloud", + "description": "The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.", + "example": "666777888999", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "cloud.account.id", + "searchable": true, + "type": "string", + }, + "cloud.availability_zone": Object { + "aggregatable": true, + "category": "cloud", + "description": "Availability zone in which this host is running.", + "example": "us-east-1c", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "cloud.availability_zone", + "searchable": true, + "type": "string", + }, + }, + }, + "container": Object { + "fields": Object { + "container.id": Object { + "aggregatable": true, + "category": "container", + "description": "Unique container id.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "container.id", + "searchable": true, + "type": "string", + }, + "container.image.name": Object { + "aggregatable": true, + "category": "container", + "description": "Name of the image the container was built on.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "container.image.name", + "searchable": true, + "type": "string", + }, + "container.image.tag": Object { + "aggregatable": true, + "category": "container", + "description": "Container image tag.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "container.image.tag", + "searchable": true, + "type": "string", + }, + }, + }, + "destination": Object { + "fields": Object { + "destination.address": Object { + "aggregatable": true, + "category": "destination", + "description": "Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "destination.address", + "searchable": true, + "type": "string", + }, + "destination.bytes": Object { + "aggregatable": true, + "category": "destination", + "description": "Bytes sent from the destination to the source.", + "example": "184", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "destination.bytes", + "searchable": true, + "type": "number", + }, + "destination.domain": Object { + "aggregatable": true, + "category": "destination", + "description": "Destination domain.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "destination.domain", + "searchable": true, + "type": "string", + }, + "destination.ip": Object { + "aggregatable": true, + "category": "destination", + "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "destination.ip", + "searchable": true, + "type": "ip", + }, + "destination.port": Object { + "aggregatable": true, + "category": "destination", + "description": "Port of the destination.", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "destination.port", + "searchable": true, + "type": "long", + }, + }, + }, + "event": Object { + "fields": Object { + "event.end": Object { + "aggregatable": true, + "category": "event", + "description": "event.end contains the date when the event ended or when the activity was last observed.", + "example": null, + "format": "", + "indexes": Array [ + "auditbeat-*", + "endgame-*", + "filebeat-*", + "packetbeat-*", + "winlogbeat-*", + ], + "name": "event.end", + "searchable": true, + "type": "date", + }, + }, + }, + "source": Object { + "fields": Object { + "source.ip": Object { + "aggregatable": true, + "category": "source", + "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "source.ip", + "searchable": true, + "type": "ip", + }, + "source.port": Object { + "aggregatable": true, + "category": "source", + "description": "Port of the source.", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "source.port", + "searchable": true, + "type": "long", + }, + }, + }, + } + } +> + Drag drop context wrapper children + `; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap index a240d5122ac9c..aa8214938c2b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/draggable_wrapper.test.tsx.snap @@ -1,443 +1,22 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DraggableWrapper rendering it renders against the snapshot 1`] = ` - - - - - - - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap index 23a540f0ce3b3..7c6e321395fa5 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/droppable_wrapper.test.tsx.snap @@ -1,430 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DroppableWrapper rendering it renders against the snapshot 1`] = ` - - - - - draggable wrapper content - - - - + + draggable wrapper content + `; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx index b8fba6fe2f6d8..1a8af9d99193a 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx @@ -28,7 +28,7 @@ describe('DragDropContextWrapper', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DragDropContextWrapper'))).toMatchSnapshot(); }); test('it renders the children', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx index a3528158a0317..f9e6bfcf7c236 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -114,6 +114,8 @@ const mapStateToProps = (state: State) => { export const DragDropContextWrapper = connect(mapStateToProps)(DragDropContextWrapperComponent); +DragDropContextWrapper.displayName = 'DragDropContextWrapper'; + const onBeforeCapture = (before: BeforeCapture) => { const x = window.pageXOffset !== undefined diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index d9b78836b450e..008ece5c7e69c 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -30,7 +30,7 @@ describe('DraggableWrapper', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); }); test('it renders the children passed to the render prop', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index c314785511201..809c46f7b53bb 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -275,6 +275,8 @@ export const DraggableWrapper = connect(null, { unRegisterProvider: dragAndDropActions.unRegisterProvider, })(DraggableWrapperComponent); +DraggableWrapper.displayName = 'DraggableWrapper'; + /** * Conditionally wraps children in an EuiPortal to ensure drag offsets are correct when dragging * from containers that have css transforms diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx index 859b30d2164dd..39abbdd4d4e38 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx @@ -30,7 +30,7 @@ describe('DroppableWrapper', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DroppableWrapper'))).toMatchSnapshot(); }); test('it renders the children when a render prop is not provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index 3f789a39832f1..2b013a665af16 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx @@ -7,7 +7,6 @@ import { rgba } from 'polished'; import * as React from 'react'; import { Droppable } from 'react-beautiful-dnd'; -import { pure } from 'recompose'; import styled from 'styled-components'; interface Props { @@ -87,7 +86,7 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string `; ReactDndDropTarget.displayName = 'ReactDndDropTarget'; -export const DroppableWrapper = pure( +export const DroppableWrapper = React.memo( ({ children = null, droppableId, diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/draggables/__snapshots__/index.test.tsx.snap index 1e9e89ad66641..63ba13306ecd8 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/draggables/__snapshots__/index.test.tsx.snap @@ -1,29 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`draggables rendering it renders the default Badge 1`] = ` - - - A child of this - - + + + A child of this + + + `; exports[`draggables rendering it renders the default DefaultDraggable 1`] = ` - - - A child of this - - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx index 5bff59494b9ad..90d8ad463b476 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx @@ -6,7 +6,6 @@ import { rgba } from 'polished'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; const Field = styled.div` @@ -28,11 +27,12 @@ Field.displayName = 'Field'; // Passing the styles directly to the component because the width is // being calculated and is recommended by Styled Components for performance // https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291 -export const DraggableFieldBadge = pure<{ fieldId: string; fieldWidth?: string }>( +export const DraggableFieldBadge = React.memo<{ fieldId: string; fieldWidth?: string }>( ({ fieldId, fieldWidth }) => ( {fieldId} ) ); + DraggableFieldBadge.displayName = 'DraggableFieldBadge'; diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx index fb49329ba1501..d3dcba9526bdd 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx @@ -108,19 +108,15 @@ describe('draggables', () => { }); test('it returns null if value is undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); test('it returns null if value is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); @@ -218,31 +214,27 @@ describe('draggables', () => { }); test('it returns null if value is undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); test('it returns null if value is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx index 2f91cdc43b797..5b219dad9c841 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx @@ -6,7 +6,6 @@ import { EuiBadge, EuiBadgeProps, EuiToolTip, IconType } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import { Omit } from '../../../common/utility_types'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; @@ -50,7 +49,7 @@ export const getDefaultWhenTooltipIsUnspecified = ({ /** * Renders the content of the draggable, wrapped in a tooltip */ -const Content = pure<{ +const Content = React.memo<{ children?: React.ReactNode; field: string; tooltipContent?: React.ReactNode; @@ -83,7 +82,7 @@ Content.displayName = 'Content'; * prevent a tooltip from being displayed, or pass arbitrary content * @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data */ -export const DefaultDraggable = pure( +export const DefaultDraggable = React.memo( ({ id, field, value, name, children, tooltipContent, queryValue }) => value != null ? ( & { * prevent a tooltip from being displayed, or pass arbitrary content * @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data */ -export const DraggableBadge = pure( +export const DraggableBadge = React.memo( ({ contextId, eventId, diff --git a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx index 06446a152bea8..15e6246f1f1ad 100644 --- a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DefaultDraggable } from '../draggables'; import { FormattedDuration } from '../formatted_duration'; @@ -16,7 +15,7 @@ export const EVENT_DURATION_FIELD_NAME = 'event.duration'; * Renders draggable text containing the value of a field representing a * duration of time, (e.g. `event.duration`) */ -export const Duration = pure<{ +export const Duration = React.memo<{ contextId: string; eventId: string; fieldName: string; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts index d7da585966758..ede0d3f394789 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts @@ -147,6 +147,10 @@ export const mockLineLayer = { }, minSize: 1, maxSize: 8, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, }, }, iconSize: { type: 'STATIC', options: { size: 10 } }, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap index f343316d88c46..b03670b2b1cd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap @@ -1,11 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Embeddable it renders 1`] = ` - - +
+

Test content

- - +
+
`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap index e88693b292a5d..6d02ccb1c6eb9 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap @@ -1,9 +1,22 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EmbeddableHeader it renders 1`] = ` - - - +
+ + + +
+ Test title +
+
+
+
+
`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx index 49f5306dc1b60..c0d70754e78bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx @@ -9,7 +9,6 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import '../../mock/ui_settings'; -import { TestProviders } from '../../mock'; import { Embeddable } from './embeddable'; jest.mock('../../lib/settings/use_kibana_ui_setting'); @@ -17,11 +16,9 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('Embeddable', () => { test('it renders', () => { const wrapper = shallow( - - -

{'Test content'}

-
-
+ +

{'Test content'}

+
); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx index 4536da3ba7b97..6387de30aa265 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx @@ -16,11 +16,7 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('EmbeddableHeader', () => { test('it renders', () => { - const wrapper = shallow( - - - - ); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts index fd17e6eaeac64..637251eb64f70 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts @@ -210,6 +210,10 @@ export const getLineLayer = (indexPatternTitle: string, indexPatternId: string) }, minSize: 1, maxSize: 8, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, }, }, iconSize: { type: 'STATIC', options: { size: 10 } }, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap index 2ef4d9df89a1b..9d39b6e59365f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap @@ -1,18 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PointToolTipContent renders correctly against snapshot 1`] = ` - - - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 1733fb3aa7480..5e1eae1649b41 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -46,7 +46,7 @@ describe('PointToolTipContent', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('PointToolTipContentComponent'))).toMatchSnapshot(); }); test('renders array filter correctly', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap index 7e1da6ae7ace3..9b6bfb1752a20 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap @@ -1,9 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` - + + + Do Something + + + + } + title={ +

+ My Super Title +

+ } /> `; diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx index 9c3dd462de153..ef2b76c9aad1c 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, IconType } from '@elastic/eui'; import React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; const EmptyPrompt = styled(EuiEmptyPrompt)` @@ -29,7 +28,7 @@ interface EmptyPageProps { title: string; } -export const EmptyPage = pure( +export const EmptyPage = React.memo( ({ actionPrimaryIcon, actionPrimaryLabel, diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap index bfb10fc385c08..4cf7cbb43cdc7 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -1,692 +1,1544 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EventDetails rendering should match snapshot 1`] = ` - - + , + "id": "table-view", + "name": "Table", } } - columnHeaders={ + tabs={ Array [ Object { - "aggregatable": true, - "category": "base", - "columnHeaderType": "not-filtered", - "description": "Date/time when the event originated. + "content": , + "id": "table-view", + "name": "Table", }, Object { - "field": "destination.port", - "originalValue": 902, - "values": Array [ - "902", - ], + "content": , + "id": "json-view", + "name": "JSON View", }, ] } - id="Y-6TfmcB0WOhS6qyMv3s" - onUpdateColumns={[MockFunction]} - onViewSelected={[MockFunction]} - timelineId="test" - toggleColumn={[MockFunction]} - view="table-view" /> - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/json_view.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/json_view.test.tsx.snap index a788b60afd6b3..caa7853fd9ec0 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/json_view.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/json_view.test.tsx.snap @@ -1,150 +1,52 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`JSON View rendering should match snapshot 1`] = ` - + +}" + width="100%" + /> + `; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx index fb1f9f0cd4e64..d8c0e46d8480b 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx @@ -21,19 +21,17 @@ describe('EventDetails', () => { describe('rendering', () => { test('should match snapshot', () => { const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx index 05690a0d20d92..519f56adff2d2 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx @@ -7,7 +7,6 @@ import { EuiCodeEditor } from '@elastic/eui'; import { set } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { DetailItem } from '../../graphql/types'; @@ -23,7 +22,7 @@ const JsonEditor = styled.div` JsonEditor.displayName = 'JsonEditor'; -export const JsonView = pure(({ data }) => ( +export const JsonView = React.memo(({ data }) => ( ( createTimeline, columns, dataProviders, + defaultFilters = [], defaultModel, defaultIndices, deleteEventQuery, @@ -158,7 +159,7 @@ const StatefulEventsViewerComponent = React.memo( id={id} dataProviders={dataProviders!} end={end} - filters={filters} + filters={[...filters, ...defaultFilters]} headerFilterGroup={headerFilterGroup} indexPattern={indexPatterns ?? { fields: [], title: '' }} isLive={isLive} @@ -201,7 +202,7 @@ const makeMapStateToProps = () => { const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getEvents = timelineSelectors.getEventsByIdSelector(); - const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { + const mapStateToProps = (state: State, { id, defaultFilters = [], defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); const events: TimelineModel = getEvents(state, id) ?? defaultModel; const { columns, dataProviders, itemsPerPage, itemsPerPageOptions, kqlMode, sort } = events; @@ -209,7 +210,7 @@ const makeMapStateToProps = () => { return { columns, dataProviders, - filters: getGlobalFiltersQuerySelector(state), + filters: [...getGlobalFiltersQuerySelector(state), ...defaultFilters], id, isLive: input.policy.kind === 'interval', itemsPerPage, diff --git a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx index a54e0803d02ea..bba32e72abc37 100644 --- a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx @@ -6,7 +6,6 @@ import { EuiIcon } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; const LinkIcon = styled(EuiIcon)` @@ -30,7 +29,7 @@ const iconType = 'popout'; * Renders an icon that indicates following the hyperlink will navigate to * content external to the app */ -export const ExternalLinkIcon = pure<{ +export const ExternalLinkIcon = React.memo<{ leftMargin?: boolean; }>(({ leftMargin = true }) => leftMargin ? ( diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap index 6ae9268966480..2ff93b2ecada4 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap @@ -1,220 +1,126 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Field Renderers #autonomousSystemRenderer it renders correctly against snapshot 1`] = ` - - + + + + - - - - - / - - - - - - + / + + + + +
`; exports[`Field Renderers #dateRenderer it renders correctly against snapshot 1`] = ` - - + - + `; exports[`Field Renderers #hostIdRenderer it renders correctly against snapshot 1`] = ` - - - - raspberrypi - - - + `; exports[`Field Renderers #hostNameRenderer it renders correctly against snapshot 1`] = ` - - - - raspberrypi - - - + `; exports[`Field Renderers #locationRenderer it renders correctly against snapshot 1`] = ` - - + + + + ,  + - - - - ,  - - - - - + + +
`; exports[`Field Renderers #reputationRenderer it renders correctly against snapshot 1`] = ` - talosIntelligence.com - + `; exports[`Field Renderers #whoisRenderer it renders correctly against snapshot 1`] = ` - - - iana.org - - + iana.org + `; diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx index 0fd63bc3f2bf2..2d69db82405ba 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx @@ -32,9 +32,7 @@ describe('Field Renderers', () => { describe('#locationRenderer', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow( - - {locationRenderer(['source.geo.city_name', 'source.geo.region_name'], mockData.complete)} - + locationRenderer(['source.geo.city_name', 'source.geo.region_name'], mockData.complete) ); expect(toJson(wrapper)).toMatchSnapshot(); @@ -59,9 +57,7 @@ describe('Field Renderers', () => { describe('#dateRenderer', () => { test('it renders correctly against snapshot', () => { - const wrapper = shallow( - {dateRenderer(mockData.complete.source!.firstSeen)} - ); + const wrapper = shallow(dateRenderer(mockData.complete.source!.firstSeen)); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -78,9 +74,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow( - - {autonomousSystemRenderer(mockData.complete.source!.autonomousSystem!, FlowTarget.source)} - + autonomousSystemRenderer(mockData.complete.source!.autonomousSystem!, FlowTarget.source) ); expect(toJson(wrapper)).toMatchSnapshot(); @@ -113,9 +107,7 @@ describe('Field Renderers', () => { ip: null, }; test('it renders correctly against snapshot', () => { - const wrapper = shallow( - {hostNameRenderer(mockData.complete.host, '10.10.10.10')} - ); + const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -158,9 +150,7 @@ describe('Field Renderers', () => { ip: ['10.10.10.10'], }; test('it renders correctly against snapshot', () => { - const wrapper = shallow( - {hostNameRenderer(mockData.complete.host, '10.10.10.10')} - ); + const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -194,9 +184,7 @@ describe('Field Renderers', () => { describe('#whoisRenderer', () => { test('it renders correctly against snapshot', () => { - const wrapper = shallowWithIntl( - {whoisRenderer('10.10.10.10')} - ); + const wrapper = shallowWithIntl(whoisRenderer('10.10.10.10')); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -208,7 +196,7 @@ describe('Field Renderers', () => { {reputationRenderer('10.10.10.10')} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx index 5df961dfceeb5..80d68dfe1b731 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx @@ -8,9 +8,8 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover, EuiText } from ' import { FormattedMessage } from '@kbn/i18n/react'; import { getOr } from 'lodash/fp'; import React, { Fragment, useState } from 'react'; -import { pure } from 'recompose'; - import styled from 'styled-components'; + import { AutonomousSystem, FlowTarget, HostEcsFields, IpOverviewData } from '../../graphql/types'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; import { DefaultDraggable } from '../draggables'; @@ -151,7 +150,7 @@ interface DefaultFieldRendererProps { // TODO: This causes breaks between elements until the ticket below is fixed // https://github.com/elastic/ingest-dev/issues/474 -export const DefaultFieldRenderer = pure( +export const DefaultFieldRenderer = React.memo( ({ attrName, displayCount = 1, diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx index 8d4e3b3928492..7b8451db2212f 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx @@ -5,7 +5,6 @@ */ import { EuiInMemoryTable } from '@elastic/eui'; -import { pure } from 'recompose'; import * as React from 'react'; import styled from 'styled-components'; @@ -33,7 +32,7 @@ interface Props { width: number; } -export const Category = pure( +export const Category = React.memo( ({ categoryId, filteredBrowserFields, fieldItems, timelineId, width }) => ( <> (({ filteredBrowserFields, categoryId, timelineId }) => ( - - - -
{categoryId}
-
-
- - - - - {getFieldCount(filteredBrowserFields[categoryId])} - - - -
-)); +export const CategoryTitle = React.memo( + ({ filteredBrowserFields, categoryId, timelineId }) => ( + + + +
{categoryId}
+
+
+ + + + + {getFieldCount(filteredBrowserFields[categoryId])} + + + +
+ ) +); CategoryTitle.displayName = 'CategoryTitle'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx index 4cc5537bec343..170cf324ca6d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx @@ -5,7 +5,6 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { pure } from 'recompose'; import * as React from 'react'; import styled from 'styled-components'; @@ -59,7 +58,7 @@ type Props = Pick void; }; -export const FieldsPane = pure( +export const FieldsPane = React.memo( ({ columnHeaders, filteredBrowserFields, diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx index ae9109bffe0db..8acb19970c268 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx @@ -13,7 +13,6 @@ import { EuiTitle, } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; @@ -65,7 +64,7 @@ interface Props { timelineId: string; } -const CountRow = pure>(({ filteredBrowserFields }) => ( +const CountRow = React.memo>(({ filteredBrowserFields }) => ( >(({ filteredBrowserFi CountRow.displayName = 'CountRow'; -const TitleRow = pure<{ +const TitleRow = React.memo<{ isEventViewer?: boolean; onOutsideClick: () => void; onUpdateColumns: OnUpdateColumns; @@ -121,7 +120,7 @@ const TitleRow = pure<{ TitleRow.displayName = 'TitleRow'; -export const Header = pure( +export const Header = React.memo( ({ isEventViewer, isSearching, diff --git a/x-pack/legacy/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap index 56432cb25c189..35fe74abff284 100644 --- a/x-pack/legacy/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap @@ -1,9 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`rendering renders correctly 1`] = ` - -

- Additional filters here. -

-
+ + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.test.tsx b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.test.tsx index adbd904c5c325..7f377a57c3e9b 100644 --- a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.test.tsx @@ -9,7 +9,7 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import '../../mock/match_media'; -import { FiltersGlobal } from './index'; +import { FiltersGlobal } from './filters_global'; describe('rendering', () => { test('renders correctly', () => { @@ -18,6 +18,7 @@ describe('rendering', () => {

{'Additional filters here.'}

); + expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx index bdda8497a8bcb..edf6f7f01ab2e 100644 --- a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx +++ b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx @@ -7,7 +7,6 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; import { Sticky } from 'react-sticky'; -import { pure } from 'recompose'; import styled, { css } from 'styled-components'; import { gutterTimeline } from '../../lib/helpers'; @@ -42,7 +41,7 @@ export interface FiltersGlobalProps { children: React.ReactNode; } -export const FiltersGlobal = pure(({ children }) => ( +export const FiltersGlobal = React.memo(({ children }) => ( {({ style, isSticky }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap index 9553ec5b7654e..ee76657c8d27a 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap @@ -1,8 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Select Flow Direction rendering it renders the basic group button for uni-direction and bi-direction 1`] = ` - + + + Unidirectional + + + Bidirectional + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap index 46053008ea09c..a9b48c8ee16be 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap @@ -1,11 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FlowTargetSelect Component rendering it renders the FlowTargetSelect 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.tsx index d5370c218a2de..2b826164063be 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.tsx @@ -7,7 +7,6 @@ import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; import React from 'react'; -import { pure } from 'recompose'; import { FlowDirection } from '../../graphql/types'; import * as i18n from './translations'; @@ -17,7 +16,7 @@ interface Props { onChangeDirection: (value: FlowDirection) => void; } -export const FlowDirectionSelect = pure(({ onChangeDirection, selectedDirection }) => ( +export const FlowDirectionSelect = React.memo(({ onChangeDirection, selectedDirection }) => ( ( +export const FlowTargetSelect = React.memo( ({ id, isLoading = false, diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap index 3aa9fd1b962b5..abdc4f4681294 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flyout/__snapshots__/index.test.tsx.snap @@ -1,16 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Flyout rendering it renders correctly against snapshot 1`] = ` - - - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx index ddc3e4f15938a..86a8952a10efa 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx @@ -37,7 +37,7 @@ describe('Flyout', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Flyout'))).toMatchSnapshot(); }); test('it renders the default flyout state as a button', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index aae8f67997156..2d347830d5b1b 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -124,3 +124,5 @@ const mapStateToProps = (state: State, { timelineId }: OwnProps) => { export const Flyout = connect(mapStateToProps, { showTimeline: timelineActions.showTimeline, })(FlyoutComponent); + +Flyout.displayName = 'Flyout'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap index 31eaf4f56d7bc..efa682cd4d18e 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/__snapshots__/index.test.tsx.snap @@ -1,22 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Pane renders correctly against snapshot 1`] = ` - - - - I am a child of flyout - - - + + + I am a child of flyout + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index 65233e55901ff..acea2d1cce468 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -44,7 +44,7 @@ describe('Pane', () => { ); - expect(toJson(EmptyComponent)).toMatchSnapshot(); + expect(toJson(EmptyComponent.find('Pane'))).toMatchSnapshot(); }); test('it should NOT let the flyout expand to take up the full width of the element that contains it', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx index 4b5ceb25befa4..f2f0cf4f980f3 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx @@ -182,3 +182,5 @@ FlyoutPaneComponent.displayName = 'FlyoutPaneComponent'; export const Pane = connect(null, { applyDeltaToWidth: timelineActions.applyDeltaToWidth, })(FlyoutPaneComponent); + +Pane.displayName = 'Pane'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx index 71820c62dd528..a517820361f9f 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx @@ -21,6 +21,10 @@ jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({ describe('formatted_bytes', () => { describe('PreferenceFormattedBytes', () => { describe('rendering', () => { + beforeEach(() => { + mockUseKibanaUiSetting.mockClear(); + }); + const bytes = '2806422'; test('renders correctly against snapshot', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap index 0f9cf1ba89f9c..d196a23bff5bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`formatted_date PreferenceFormattedDate rendering renders correctly against snapshot 1`] = ` - +> + 2019-02-25T22:27:05.000Z + `; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx index bb0b947f149f4..df361a06d3805 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx @@ -38,7 +38,8 @@ describe('formatted_date', () => { .format(config.dateFormat); test('renders correctly against snapshot', () => { - const wrapper = shallow(); + mockUseKibanaUiSetting.mockImplementation(() => [null]); + const wrapper = mount(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 32c064096fcf9..37bf3653f3b62 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -7,7 +7,6 @@ import moment from 'moment-timezone'; import * as React from 'react'; import { FormattedRelative } from '@kbn/i18n/react'; -import { pure } from 'recompose'; import { DEFAULT_DATE_FORMAT, @@ -19,7 +18,7 @@ import { getOrEmptyTagFromValue } from '../empty_value'; import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; -export const PreferenceFormattedDate = pure<{ value: Date }>(({ value }) => { +export const PreferenceFormattedDate = React.memo<{ value: Date }>(({ value }) => { const [dateFormat] = useKibanaUiSetting(DEFAULT_DATE_FORMAT); const [dateFormatTz] = useKibanaUiSetting(DEFAULT_DATE_FORMAT_TZ); const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER); @@ -43,7 +42,7 @@ PreferenceFormattedDate.displayName = 'PreferenceFormattedDate'; * - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm) * - the raw date value (e.g. 2019-03-22T00:47:46Z) */ -export const FormattedDate = pure<{ +export const FormattedDate = React.memo<{ fieldName: string; value?: string | number | null; }>( diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx index c97fc7bdc2428..8afbafe57af4a 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx @@ -5,12 +5,11 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { getFormattedDurationString } from './helpers'; import { FormattedDurationTooltip } from './tooltip'; -export const FormattedDuration = pure<{ +export const FormattedDuration = React.memo<{ maybeDurationNanoseconds: string | number | object | undefined | null; tooltipTitle?: string; }>(({ maybeDurationNanoseconds, tooltipTitle }) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx index 08f4a412caf51..1372b3ef10920 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx @@ -6,7 +6,6 @@ import { EuiToolTip } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; @@ -18,7 +17,7 @@ const P = styled.p` P.displayName = 'P'; -export const FormattedDurationTooltipContent = pure<{ +export const FormattedDurationTooltipContent = React.memo<{ maybeDurationNanoseconds: string | number | object | undefined | null; tooltipTitle?: string; }>(({ maybeDurationNanoseconds, tooltipTitle }) => ( @@ -35,7 +34,7 @@ export const FormattedDurationTooltipContent = pure<{ FormattedDurationTooltipContent.displayName = 'FormattedDurationTooltipContent'; -export const FormattedDurationTooltip = pure<{ +export const FormattedDurationTooltip = React.memo<{ children: JSX.Element; maybeDurationNanoseconds: string | number | object | undefined | null; tooltipTitle?: string; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx index 81f5cbfe2308b..8dcb558122d01 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx @@ -6,7 +6,6 @@ import { isArray, isEmpty, isString, uniq } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; @@ -60,7 +59,7 @@ const getDataProvider = ({ and: [], }); -const NonDecoratedIp = pure<{ +const NonDecoratedIp = React.memo<{ contextId: string; eventId: string; fieldName: string; @@ -92,7 +91,7 @@ const NonDecoratedIp = pure<{ NonDecoratedIp.displayName = 'NonDecoratedIp'; -const AddressLinks = pure<{ +const AddressLinks = React.memo<{ addresses: string[]; contextId: string; eventId: string; @@ -128,7 +127,7 @@ const AddressLinks = pure<{ AddressLinks.displayName = 'AddressLinks'; -export const FormattedIp = pure<{ +export const FormattedIp = React.memo<{ contextId: string; eventId: string; fieldName: string; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap index 665a5c75f3684..849f3616524cc 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap @@ -1,7 +1,107 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderGlobal it renders 1`] = ` - - - + + + + + + + + + + + + + + + + + + + + + + Add data + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index ebd1da634ed1a..b3eb599af9407 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -8,7 +8,6 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import { TestProviders } from '../../mock'; import '../../mock/match_media'; import '../../mock/ui_settings'; import { HeaderGlobal } from './index'; @@ -23,11 +22,7 @@ jest.mock('../search_bar', () => ({ describe('HeaderGlobal', () => { test('it renders', () => { - const wrapper = shallow( - - - - ); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap index 0fe2890dc9f24..a91d8fce87dac 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -1,23 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderPage it renders 1`] = ` - - + -

- Test supplement -

-
-
+ + +

+ Test title + + +

+
+ + +
+ +

+ Test supplement +

+
+ + `; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx index 9c50a915b7ba8..c20f3c7185e66 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -18,17 +18,15 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('HeaderPage', () => { test('it renders', () => { const wrapper = shallow( - - -

{'Test supplement'}

-
-
+ +

{'Test supplement'}

+
); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap index ecd2b15a841f6..d4c3763f51460 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_section/__snapshots__/index.test.tsx.snap @@ -1,9 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderSection it renders 1`] = ` - - - +
+ + + + + +

+ Test title +

+
+
+
+
+
+
`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index 4a6da9c80968f..8606758c68d2c 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -17,11 +17,7 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('HeaderSection', () => { test('it renders', () => { - const wrapper = shallow( - - - - ); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); 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 43fd8e653f3d8..d42ee08e86407 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 @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect } from 'react'; -import { pure } from 'recompose'; +import React, { useEffect } from 'react'; import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; -export const HelpMenu = pure<{}>(() => { +export const HelpMenu = React.memo(() => { useEffect(() => { chrome.helpExtension.set({ appName: i18n.translate('xpack.siem.chrome.help.appName', { diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx index 56bd86310acad..6908aba542e4c 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx @@ -11,7 +11,6 @@ import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import styled from 'styled-components'; -import { pure } from 'recompose'; import { inputsModel, inputsSelectors, State } from '../../store'; import { InputsModelId } from '../../store/inputs/constants'; import { inputsActions } from '../../store/inputs'; @@ -58,7 +57,7 @@ interface InspectButtonDispatch { type InspectButtonProps = OwnProps & InspectButtonReducer & InspectButtonDispatch; -const InspectButtonComponent = pure( +const InspectButtonComponent = React.memo( ({ compact = false, inputId = 'global', diff --git a/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap index d75a0f054775a..0199742242e59 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap @@ -1,10 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Port renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx index ceec48951a198..8c327989963b4 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { FormattedFieldValue } from '../timeline/body/renderers/formatted_field'; @@ -18,7 +17,7 @@ const IP_FIELD_TYPE = 'ip'; * Renders text containing a draggable IP address (e.g. `source.ip`, * `destination.ip`) that contains a hyperlink */ -export const Ip = pure<{ +export const Ip = React.memo<{ contextId: string; eventId: string; fieldName: string; diff --git a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx index 3148efbb3050a..950ab252ad0bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; @@ -27,7 +26,7 @@ Ja3FingerprintLabel.displayName = 'Ja3FingerprintLabel'; * using TLS traffic to be identified, which is possible because SSL * negotiations happen in the clear */ -export const Ja3Fingerprint = pure<{ +export const Ja3Fingerprint = React.memo<{ eventId: string; contextId: string; fieldName: string; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap index 5902768383cb0..c5086c8cde285 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap @@ -1,14 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LinkIcon it renders 1`] = ` - - + + Test link - - + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx index 451db49028ee1..7f9133a0de7c0 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx @@ -17,11 +17,9 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('LinkIcon', () => { test('it renders', () => { const wrapper = shallow( - - - {'Test link'} - - + + {'Test link'} + ); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 0125b52e3ad33..5a7f6ef1274c9 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom'; -import { pure } from 'recompose'; import { SiemPageName } from '../../pages/home/types'; import { HostsTableType } from '../../store/hosts/model'; @@ -26,7 +25,7 @@ interface LinkToPageProps { match: RouteMatch<{}>; } -export const LinkToPage = pure(({ match }) => ( +export const LinkToPage = React.memo(({ match }) => ( ( +export const HostDetailsLink = React.memo<{ children?: React.ReactNode; hostName: string }>( ({ children, hostName }) => ( {children ? children : hostName} @@ -22,7 +21,7 @@ export const HostDetailsLink = pure<{ children?: React.ReactNode; hostName: stri HostDetailsLink.displayName = 'HostDetailsLink'; -export const IPDetailsLink = pure<{ children?: React.ReactNode; ip: string }>( +export const IPDetailsLink = React.memo<{ children?: React.ReactNode; ip: string }>( ({ children, ip }) => ( {children ? children : ip} @@ -33,7 +32,7 @@ export const IPDetailsLink = pure<{ children?: React.ReactNode; ip: string }>( IPDetailsLink.displayName = 'IPDetailsLink'; // External Links -export const GoogleLink = pure<{ children?: React.ReactNode; link: string }>( +export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>( ({ children, link }) => ( {children ? children : link} @@ -43,7 +42,7 @@ export const GoogleLink = pure<{ children?: React.ReactNode; link: string }>( GoogleLink.displayName = 'GoogleLink'; -export const PortOrServiceNameLink = pure<{ +export const PortOrServiceNameLink = React.memo<{ children?: React.ReactNode; portOrServiceName: number | string; }>(({ children, portOrServiceName }) => ( @@ -60,21 +59,22 @@ export const PortOrServiceNameLink = pure<{ PortOrServiceNameLink.displayName = 'PortOrServiceNameLink'; -export const Ja3FingerprintLink = pure<{ children?: React.ReactNode; ja3Fingerprint: string }>( - ({ children, ja3Fingerprint }) => ( - - {children ? children : ja3Fingerprint} - - ) -); +export const Ja3FingerprintLink = React.memo<{ + children?: React.ReactNode; + ja3Fingerprint: string; +}>(({ children, ja3Fingerprint }) => ( + + {children ? children : ja3Fingerprint} + +)); Ja3FingerprintLink.displayName = 'Ja3FingerprintLink'; -export const CertificateFingerprintLink = pure<{ +export const CertificateFingerprintLink = React.memo<{ children?: React.ReactNode; certificateFingerprint: string; }>(({ children, certificateFingerprint }) => ( @@ -91,7 +91,7 @@ export const CertificateFingerprintLink = pure<{ CertificateFingerprintLink.displayName = 'CertificateFingerprintLink'; -export const ReputationLink = pure<{ children?: React.ReactNode; domain: string }>( +export const ReputationLink = React.memo<{ children?: React.ReactNode; domain: string }>( ({ children, domain }) => ( ( +export const VirusTotalLink = React.memo<{ children?: React.ReactNode; link: string }>( ({ children, link }) => ( VirusTotalLink.displayName = 'VirusTotalLink'; -export const WhoIsLink = pure<{ children?: React.ReactNode; domain: string }>( +export const WhoIsLink = React.memo<{ children?: React.ReactNode; domain: string }>( ({ children, domain }) => ( {children ? children : domain} diff --git a/x-pack/legacy/plugins/siem/public/components/loader/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/loader/__snapshots__/index.test.tsx.snap index 440193c9e0dfd..0885f15b1efba 100644 --- a/x-pack/legacy/plugins/siem/public/components/loader/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/loader/__snapshots__/index.test.tsx.snap @@ -1,11 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`rendering renders correctly 1`] = ` - - Loading - + + + + + + +

+ Loading +

+
+
+
+ `; diff --git a/x-pack/legacy/plugins/siem/public/components/loader/index.tsx b/x-pack/legacy/plugins/siem/public/components/loader/index.tsx index 55628fe2e8d33..be2ce3dde951c 100644 --- a/x-pack/legacy/plugins/siem/public/components/loader/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loader/index.tsx @@ -14,7 +14,6 @@ import { } from '@elastic/eui'; import { rgba } from 'polished'; import React from 'react'; -import { pure } from 'recompose'; import styled, { css } from 'styled-components'; const Aside = styled.aside<{ overlay?: boolean; overlayBackground?: string }>` @@ -56,9 +55,10 @@ export interface LoaderProps { overlay?: boolean; overlayBackground?: string; size?: EuiLoadingSpinnerSize; + children?: React.ReactChild; } -export const Loader = pure(({ children, overlay, overlayBackground, size }) => ( +export const Loader = React.memo(({ children, overlay, overlayBackground, size }) => (