diff --git a/.i18nrc.json b/.i18nrc.json index e5ba6762da154..a1c49ae03f359 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,8 +17,10 @@ "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "management": "src/legacy/core_plugins/management", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", + "kibana_utils": "src/plugins/kibana_utils", "navigation": "src/legacy/core_plugins/navigation", "newsfeed": "src/plugins/newsfeed", "regionMap": "src/legacy/core_plugins/region_map", diff --git a/NOTICE.txt b/NOTICE.txt index 989bb6f754a09..230e511746022 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -186,32 +186,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -This product includes code that is based on facebookincubator/idx, which was -available under a "MIT" license. - -MIT License - -Copyright (c) 2013-present, Facebook, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - --- This product includes code that was extracted from angular@1.3. Original license: diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 350a3c2a997cf..77a2bfe77b4ab 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -282,7 +282,7 @@ The `FunctionalTestRunner` comes with three built-in services: * Source: {blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] * Designed primary for use in services * Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously -* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup`, `phaseStart`, `phaseEnd` +* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup` [float] ===== Kibana Services 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/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 5605ed5c56688..39c87d97af4ba 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -147,6 +147,36 @@ will default to `true`. `logging.quiet:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output other than error messages. +`logging.rotate:`:: [experimental] Specifies the options for the logging rotate feature. +When not defined, all the sub options defaults would be applied. +The following example shows a valid logging rotate configuration: ++ +-- + logging.rotate: + enabled: true + everyBytes: 10485760 + keepFiles: 10 +-- + +`logging.rotate.enabled:`:: [experimental] *Default: false* Set the value of this setting to `true` to +enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +that feature would not take any effect. + +`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the +limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and +this option should be at least greater than 1024. + +`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep +on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` +option has to be in the range of 2 to 1024 files. + +`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case +the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds. + +`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring +the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, +the `polling` method could be used enabling that option. + `logging.silent:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output. 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 c3d19367e8b92..8ccb138a3aced 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": [ @@ -112,7 +113,7 @@ "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -149,6 +150,7 @@ "cache-loader": "^4.1.0", "chalk": "^2.4.2", "check-disk-space": "^2.1.0", + "chokidar": "3.2.1", "color": "1.0.3", "commander": "3.0.0", "compare-versions": "3.5.1", @@ -165,6 +167,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 +231,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", @@ -357,8 +361,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -370,7 +374,6 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chokidar": "3.2.1", "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 98fa62021b5bb..a7bce739ba8e3 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -28,6 +28,11 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: '@kbn/elastic-idx', + to: false, + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + }, { from: 'x-pack', toRelative: 'x-pack', diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index ee65a1cf79148..7917297883b03 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d1b7bc20dd9f9..0de9318ea0c27 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -35,13 +35,6 @@ const plugins = [ // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), ]; -const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test'; - -// Only load the idx plugin in non-test environments, since it conflicts with -// Jest's coverage mapping. -if (!isTestEnv) { - plugins.push(require.resolve('@kbn/elastic-idx/babel')); -} module.exports = { presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')], diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 1913301e21a76..e554859928c0b 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -12,7 +12,6 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", - "@kbn/elastic-idx": "1.0.0", "babel-plugin-add-module-exports": "^1.0.2", "babel-plugin-filter-imports": "^3.0.0", "babel-plugin-transform-define": "^1.3.1", diff --git a/packages/kbn-elastic-idx/.npmignore b/packages/kbn-elastic-idx/.npmignore deleted file mode 100644 index ece13b72c93ea..0000000000000 --- a/packages/kbn-elastic-idx/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -/tsconfig.json -/src -/babel/index.test.js diff --git a/packages/kbn-elastic-idx/README.md b/packages/kbn-elastic-idx/README.md deleted file mode 100644 index 81cd5d0f86e9b..0000000000000 --- a/packages/kbn-elastic-idx/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Kibana elastic-idx Library -========================== - -The `@kbn/elastic-idx` package provides the `idx` function used for optional -chaining. Currently, the optional chaining draft is in stage 1, making it too -uncertain to add syntax support within Kibana. Other optional chaining -libraries require the Proxy object to be polyfilled for browser support, -however, this polyfill is not fully supported across all browsers that Kibana -requires. The facebookincubator `idx` project -(https://github.com/facebookincubator/idx) provides an answer to this with a -specific implementation that is understood by TypeScript so that type -information does not get lost (unlike lodash get) The `@kbn/elastic-idx` -library makes use the `idx` idiom but differs in the way null values within the -property chain are handled. - -Similar to the facebookincubator `idx` project, `@kbn/elastic-idx` also -provides the Babel plugin to transform `idx()` function calls into the expanded -form. This Babel plugin was based off the facebookincubator `idx` Babel -plugin, since the invocation syntax is almost identical, but the transformed -code differs to match how the `@kbn/elastic-idx` library treats null values. - -App Usage ----------- -Within Kibana, `@kbn/elastic-idx` can be imported and used in any JavaScript or -TypeScript project: - -``` -import { idx } from '@kbn/elastic-idx'; - -const obj0 = { a: { b: { c: { d: 'iamdefined' } } } }; -const obj1 = { a: { b: null } }; - -idx(obj0, _ => _.a.b.c.d); // returns 'iamdefined' -idx(obj1, _ => _.a.b.c.e); // returns undefined -idx(obj1, _ => _.a.b); // returns null -``` - -Build Optimization -------------------- -Similar to the facebookincubator `idx` project, it is NOT RECOMMENDED to use -idx in shipped app code. The implementation details which make -`@kbn/elastic-idx` possible comes at a non-negligible performance cost. This -usually isn't noticable during development, but for production builds, it is -recommended to transform idx calls into native, expanded form JS. Use the -plugin `@kbn/elastic-idx/babel` within your Babel configuration: - -``` -{ "plugins": [ "@kbn/elastic-idx/babel" ] } -``` - -The resulting Babel transforms the following: - -``` -import { idx } from '@kbn/elastic-idx'; -const obj = { a: { b: { c: { d: 'iamdefined' } } } }; -idx(obj, _ => _.a.b.c.d); -``` - -into this: - -``` -obj != null && -obj.a != null && -obj.a.b != null && -obj.a.b.c != null ? -obj.a.b.c.d : undefined -``` - -Note that this also removes the import statement from the source code, since it -no longer needs to be bundled. - -Testing --------- - -Tests can be run with `npm test`. This includes "functional" tests that -transform and evaluate idx calls. diff --git a/packages/kbn-elastic-idx/babel/index.js b/packages/kbn-elastic-idx/babel/index.js deleted file mode 100644 index 89171e3565530..0000000000000 --- a/packages/kbn-elastic-idx/babel/index.js +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint strict: 0, new-cap: 0 */ - -'use strict'; -module.exports = context => { - const t = context.types; - - const idxRe = /\bidx\b/; - - function checkIdxArguments(file, node) { - const args = node.arguments; - if (args.length !== 2) { - throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.'); - } - const arrowFunction = args[1]; - if (!t.isArrowFunctionExpression(arrowFunction)) { - throw file.buildCodeFrameError( - arrowFunction, - 'The second argument supplied to `idx` must be an arrow function.' - ); - } - if (!t.isExpression(arrowFunction.body)) { - throw file.buildCodeFrameError( - arrowFunction.body, - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - } - if (arrowFunction.params.length !== 1) { - throw file.buildCodeFrameError( - arrowFunction.params[2] || arrowFunction, - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - } - const input = arrowFunction.params[0]; - if (!t.isIdentifier(input)) { - throw file.buildCodeFrameError( - arrowFunction.params[0], - 'The parameter supplied to `idx` must be an identifier.' - ); - } - } - - function checkIdxBindingNode(file, node) { - if (t.isImportDeclaration(node)) { - // E.g. `import '...'` - if (node.specifiers.length === 0) { - throw file.buildCodeFrameError(node, 'The idx import must have a value.'); - } - // E.g. `import A, {B} from '...'` - // `import A, * as B from '...'` - // `import {A, B} from '...'` - if (node.specifiers.length > 1) { - throw file.buildCodeFrameError( - node.specifiers[1], - 'The idx import must be a single specifier.' - ); - } - // `importKind` is not a property unless flow syntax is enabled. - // On specifiers, `importKind` is not "value" when it's not a type, it's - // `null`. - // E.g. `import type {...} from '...'` - // `import typeof {...} from '...'` - // `import {type ...} from '...'`. - // `import {typeof ...} from '...'` - if ( - node.importKind === 'type' || - node.importKind === 'typeof' || - node.specifiers[0].importKind === 'type' || - node.specifiers[0].importKind === 'typeof' - ) { - throw file.buildCodeFrameError(node, 'The idx import must be a value import.'); - } - } else if (t.isVariableDeclarator(node)) { - // E.g. var {idx} or var [idx] - if (!t.isIdentifier(node.id)) { - throw file.buildCodeFrameError( - node.specifiers[0], - 'The idx declaration must be an identifier.' - ); - } - } - } - - class UnsupportedNodeTypeError extends Error { - constructor(node, ...params) { - super(`Node type is not supported: ${node.type}`, ...params); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, UnsupportedNodeTypeError); - } - - this.name = 'UnsupportedNodeTypeError'; - } - } - - function getDeepProperties(node, properties = [], computedProperties = new Set()) { - if (t.isMemberExpression(node)) { - if (node.computed) { - computedProperties.add(node.property); - } - return getDeepProperties(node.object, [node.property, ...properties], computedProperties); - } else if (t.isIdentifier(node)) { - return [[node, ...properties], computedProperties]; - } - - throw new UnsupportedNodeTypeError(node); - } - - function buildMemberChain(properties, computedProperties) { - if (properties.length > 1) { - const lead = properties.slice(0, properties.length - 1); - const last = properties[properties.length - 1]; - return t.MemberExpression( - buildMemberChain(lead, computedProperties), - last, - computedProperties.has(last) - ); - } else if (properties.length === 1) { - return properties[0]; - } - return t.identifier('undefined'); - } - - function buildExpandedMemberNullChecks( - leadingProperties = [], - trailingProperties = [], - computedProperties - ) { - const propertyChainNullCheck = t.BinaryExpression( - '!=', - buildMemberChain(leadingProperties, computedProperties), - t.NullLiteral() - ); - - if (trailingProperties.length <= 1) { - return propertyChainNullCheck; - } - - const [headTrailingProperty, ...tailProperties] = trailingProperties; - - return t.LogicalExpression( - '&&', - propertyChainNullCheck, - buildExpandedMemberNullChecks( - [...leadingProperties, headTrailingProperty], - tailProperties, - computedProperties - ) - ); - } - - function buildExpandedMemberAccess(node, state) { - let baseNode; - let properties; - let computedProperties; - - try { - [[baseNode, ...properties], computedProperties] = getDeepProperties(node); - } catch (error) { - if (error instanceof UnsupportedNodeTypeError) { - throw state.file.buildCodeFrameError( - node, - 'idx callbacks may only access properties on the callback parameter.' - ); - } - - throw error; - } - - if (baseNode.name !== state.base.name) { - throw state.file.buildCodeFrameError( - node, - 'The parameter of the arrow function supplied to `idx` must match ' + - 'the base of the body expression.' - ); - } - return t.ConditionalExpression( - buildExpandedMemberNullChecks([state.input], properties, computedProperties), - buildMemberChain([state.input, ...properties], computedProperties), - t.identifier('undefined') - ); - } - - function visitIdxCallExpression(path, state) { - const node = path.node; - checkIdxArguments(state.file, node); - const replacement = buildExpandedMemberAccess(node.arguments[1].body, { - file: state.file, - input: node.arguments[0], - base: node.arguments[1].params[0], - }); - path.replaceWith(replacement); - } - - function isIdxImportOrRequire(node, name) { - if (t.isImportDeclaration(node)) { - if (name instanceof RegExp) { - return name.test(node.source.value); - } else { - return t.isStringLiteral(node.source, { value: name }); - } - } else if (t.isVariableDeclarator(node)) { - return ( - t.isCallExpression(node.init) && - t.isIdentifier(node.init.callee, { name: 'require' }) && - (name instanceof RegExp - ? name.test(node.init.arguments[0].value) - : t.isLiteral(node.init.arguments[0], { value: name })) - ); - } else { - return false; - } - } - - const declareVisitor = { - 'ImportDeclaration|VariableDeclarator'(path, state) { - if (!isIdxImportOrRequire(path.node, state.importName)) { - return; - } - - checkIdxBindingNode(state.file, path.node); - - const bindingName = t.isImportDeclaration(path.node) - ? path.node.specifiers[0].local.name - : path.node.id.name; - const idxBinding = path.scope.getOwnBinding(bindingName); - - idxBinding.constantViolations.forEach(refPath => { - throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.'); - }); - - let didTransform = false; - let didSkip = false; - - // Traverse the references backwards to process inner calls before - // outer calls. - idxBinding.referencePaths - .slice() - .reverse() - .forEach(refPath => { - if (refPath.node === idxBinding.node) { - // Do nothing... - } else if (refPath.parentPath.isMemberExpression()) { - visitIdxCallExpression(refPath.parentPath.parentPath, state); - didTransform = true; - } else if (refPath.parentPath.isCallExpression()) { - visitIdxCallExpression(refPath.parentPath, state); - didTransform = true; - } else { - // Should this throw? - didSkip = true; - } - }); - if (didTransform && !didSkip) { - path.remove(); - } - }, - }; - - return { - visitor: { - Program(path, state) { - const importName = state.opts.importName || '@kbn/elastic-idx'; - // If there can't reasonably be an idx call, exit fast. - if (importName !== '@kbn/elastic-idx' || idxRe.test(state.file.code)) { - // We're very strict about the shape of idx. Some transforms, like - // "babel-plugin-transform-async-to-generator", will convert arrow - // functions inside async functions into regular functions. So we do - // our transformation before any one else interferes. - const newState = { file: state.file, importName }; - path.traverse(declareVisitor, newState); - } - }, - }, - }; -}; diff --git a/packages/kbn-elastic-idx/babel/index.test.js b/packages/kbn-elastic-idx/babel/index.test.js deleted file mode 100644 index c4aee5266d6fa..0000000000000 --- a/packages/kbn-elastic-idx/babel/index.test.js +++ /dev/null @@ -1,694 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -'use strict'; // eslint-disable-line strict - -jest.autoMockOff(); - -const { transformSync: babelTransform } = require('@babel/core'); -const babelPluginIdx = require('./index'); -const transformAsyncToGenerator = require('@babel/plugin-transform-async-to-generator'); -const vm = require('vm'); - -function transform(source, plugins, options) { - return babelTransform(source, { - plugins: plugins || [[babelPluginIdx, options]], - babelrc: false, - highlightCode: false, - }).code; -} - -const asyncToGeneratorHelperCode = ` -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} - -function _asyncToGenerator(fn) { - return function() { - var self = this, - args = arguments; - return new Promise(function(resolve, reject) { - var gen = fn.apply(self, args); - - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - _next(undefined); - }); - }; -} -`; - -function stringByTrimmingSpaces(string) { - return string.replace(/\s+/g, ''); -} - -expect.extend({ - toTransformInto: (input, expected) => { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - const actual = transform(code, plugins, options); - const pass = stringByTrimmingSpaces(actual) === stringByTrimmingSpaces(expected); - return { - pass, - message: () => - 'Expected input to transform into:\n' + expected + '\n' + 'Instead, got:\n' + actual, - }; - }, - toThrowTransformError: (input, expected) => { - try { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - transform(code, plugins, options); - } catch (error) { - const actual = /^.+:\s*(.*)/.exec(error.message)[1]; // Strip "undefined: " and code snippet - return { - pass: actual === expected, - message: () => - 'Expected transform to throw "' + expected + '", but instead ' + 'got "' + actual + '".', - }; - } - return { - pass: false, - message: () => 'Expected transform to throw "' + expected + '".', - }; - }, - toReturn: (input, expected) => { - const code = transform(input, undefined); - const actual = vm.runInNewContext(code); - return { - pass: actual === expected, - message: () => 'Expected "' + expected + '" but got "' + actual + '".', - }; - }, -}); - -describe('kbn-babel-plugin-apm-idx', () => { - it('transforms member expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c.d.e); - `).toTransformInto(` - base != null && base.b != null && base.b.c != null && base.b.c.d != null - ? base.b.c.d.e - : undefined; - `); - }); - - it('throws on call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c(...foo)().d(bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms bracket notation', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"][0][c + d]); - `).toTransformInto(` - base != null && base["b"] != null && base["b"][0] != null ? base["b"][0][c + d] : undefined; - `); - }); - - it('throws on bracket notation call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"](...foo)()[0][c + d](bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms combination of both member access notations', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.a["b"].c[d[e[f]]].g); - `).toTransformInto(` - base != null && base.a != null && base.a["b"] != null && base.a["b"].c != null && base.a["b"].c[d[e[f]]] != null - ? base.a["b"].c[d[e[f]]].g - : undefined; - `); - }); - - it('transforms if the base is an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(this.props.base[5], _ => _.property); - `).toTransformInto(` - this.props.base[5] != null ? this.props.base[5].property : undefined; - `); - }); - - it('throws if the arrow function has more than one param', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, (a, b) => _.property); - `).toThrowTransformError( - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - }); - - it('throws if the arrow function has an invalid base', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, a => b.property) - `).toThrowTransformError( - 'The parameter of the arrow function supplied to `idx` must match the ' + - 'base of the body expression.' - ); - }); - - it('throws if the arrow function expression has non-properties/methods', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => (_.a++).b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('throws if the body of the arrow function is not an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => {}) - `).toThrowTransformError( - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - }); - - it('ignores non-function call idx', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `); - }); - - it('throws if idx is called with zero arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with one argument', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with three arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1, 2, 3); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('transforms idx calls as part of another expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - paddingStatement(); - a = idx(base, _ => _.b[c]); - `).toTransformInto(` - paddingStatement(); - a = base != null && base.b != null ? base.b[c] : undefined; - `); - }); - - it('transforms nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toTransformInto(` - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ) != null - && - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e != null ? - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e.f : - undefined; - `); - }); - - it('transforms idx calls inside async functions (plugin order #1)', () => { - expect({ - plugins: [babelPluginIdx, transformAsyncToGenerator], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls inside async functions (plugin order #2)', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls in async methods', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - class Foo { - async bar() { - idx(base, _ => _.b); - return this; - } - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - class Foo { - bar() { - var _this = this; - - return _asyncToGenerator(function* () { - base != null ? base.b : undefined; - return _this; - })(); - } - } - `); - }); - - it('transforms idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms deep idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms deep idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms idx calls when an idx is called as a member function on the binding in scope', () => { - expect(` - const elastic_idx = require("@kbn/elastic-idx"); - const result = elastic_idx.idx(base, _ => _.a.b.c.d); - `).toTransformInto(` - const result = base != null && - base.a != null && - base.a.b != null && - base.a.b.c != null ? - base.a.b.c.d : - undefined; - `); - }); - - it('throws on base call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _().b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms when the idx parent is a scope creating expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - (() => idx(base, _ => _.b)); - `).toTransformInto(` - () => base != null ? base.b : undefined; - `); - }); - - it('throws if redefined before use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx = null; - idx(base, _ => _.b); - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if redefined after use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - idx = null; - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if there is a duplicate declaration', () => { - expect(() => - transform(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - function idx() {} - `) - ).toThrow(); - }); - - it('handles sibling scopes with unique idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - base != null ? base.b : undefined; - } - `); - }); - - it('handles sibling scopes with and without idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - idx(base, _ => _.b); - } - `); - }); - - it('handles nested scopes with shadowing', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - function aaa() { - idx(base, _ => _.b); - function bbb(idx) { - idx(base, _ => _.b); - } - } - `).toTransformInto(` - base != null ? base.b : undefined; - function aaa() { - base != null ? base.b : undefined; - function bbb(idx) { - idx(base, _ => _.b); - } - } - `); - }); - - it('handles named idx import', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('throws on default plus named import', () => { - expect(` - import idx, {foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on default plus namespace import', () => { - expect(` - import idx, * as foo from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on named default plus other import', () => { - expect(` - import {default as idx, foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('unused idx import should be left alone', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - `); - }); - - it('allows configuration of the import name', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the import name', () => { - expect({ - code: ` - import { idx } from '@kbn/elastic-idx'; - import { idx as i_d_x } from 'i_d_x'; - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - it('allows configuration of the require name as a string', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('allows configuration of the require name as a RegExp', () => { - expect({ - code: ` - import { idx } from '../../common/idx'; - idx(base, _ => _.b); - `, - options: { importName: /.*idx$/ }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the require name', () => { - expect({ - code: ` - const idx = require('@kbn/elastic-idx'); - const i_d_x = require('i_d_x'); - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - const idx = require('@kbn/elastic-idx'); - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - describe('functional', () => { - it('works with only properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: 2}}}; - idx(base, _ => _.a.b.c); - `).toReturn(2); - }); - - it('works with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {}}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with null properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: null}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: {d: {e: {f: 2}}}}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(2); - }); - - it('works with nested idx calls with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: null}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(undefined); - }); - }); -}); diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json deleted file mode 100644 index 9532983942d6b..0000000000000 --- a/packages/kbn-elastic-idx/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@kbn/elastic-idx", - "version": "1.0.0", - "private": true, - "license": "Apache-2.0", - "description": "Library for optional chaining & the Babel plugin to transpile idx calls to plain, optimized JS", - "main": "target/index.js", - "types": "target/index.d.js", - "repository": { - "type": "git", - "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-elastic-idx" - }, - "scripts": { - "build": "tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch", - "test": "jest" - }, - "devDependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "jest": "^24.9.0", - "typescript": "3.7.2" - }, - "jest": { - "testEnvironment": "node" - } -} diff --git a/packages/kbn-elastic-idx/src/index.ts b/packages/kbn-elastic-idx/src/index.ts deleted file mode 100644 index eeb0df2747a14..0000000000000 --- a/packages/kbn-elastic-idx/src/index.ts +++ /dev/null @@ -1,65 +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. - */ - -/** - * DeepRequiredArray - * Nested array condition handler - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface DeepRequiredArray extends Array> {} - -/** - * DeepRequiredObject - * Nested object condition handler - */ -type DeepRequiredObject = { [P in keyof T]-?: DeepRequired }; - -/** - * Function that has deeply required return type - */ -type FunctionWithRequiredReturnType any> = T extends ( - ...args: infer A -) => infer R - ? (...args: A) => DeepRequired - : never; - -/** - * DeepRequired - * Required that works for deeply nested structure - */ -type DeepRequired = NonNullable extends never - ? T - : T extends any[] - ? DeepRequiredArray - : T extends (...args: any[]) => any - ? FunctionWithRequiredReturnType - : NonNullable extends object - ? DeepRequiredObject> - : T; - -export function idx( - input: T1, - accessor: (input: NonNullable>) => T2 -): T2 | undefined { - try { - return accessor(input as NonNullable>); - } catch (error) { - return undefined; - } -} diff --git a/packages/kbn-elastic-idx/tsconfig.json b/packages/kbn-elastic-idx/tsconfig.json deleted file mode 100644 index 27833d7594031..0000000000000 --- a/packages/kbn-elastic-idx/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "./target" - }, - "include": ["src/**/*"] -} diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js index 3ff674c89682d..37ea49172d2c4 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js @@ -29,18 +29,19 @@ export default function () { services: { hookIntoLIfecycle({ getService }) { const log = getService('log'); + const lifecycle = getService('lifecycle') - getService('lifecycle') - .on('testFailure', async (err, test) => { - log.info('testFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); - }) - .on('testHookFailure', async (err, test) => { - log.info('testHookFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); - }); + lifecycle.testFailure.add(async (err, test) => { + log.info('testFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); + + lifecycle.testHookFailure.add(async (err, test) => { + log.info('testHookFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); } }, mochaReporter: { diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 459c52997e229..fcba9691b1772 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -18,10 +18,11 @@ */ import { ToolingLog } from '@kbn/dev-utils'; -import { Suite, Test } from './fake_mocha_types'; +import { Suite, Test } from './fake_mocha_types'; import { - createLifecycle, + Lifecycle, + LifecyclePhase, readConfigFile, ProviderCollection, readProviderSpec, @@ -31,7 +32,7 @@ import { } from './lib'; export class FunctionalTestRunner { - public readonly lifecycle = createLifecycle(); + public readonly lifecycle = new Lifecycle(); private closed = false; constructor( @@ -39,13 +40,12 @@ export class FunctionalTestRunner { private readonly configFile: string, private readonly configOverrides: any ) { - this.lifecycle.on('phaseStart', name => { - log.verbose('starting %j lifecycle phase', name); - }); - - this.lifecycle.on('phaseEnd', name => { - log.verbose('ending %j lifecycle phase', name); - }); + for (const [key, value] of Object.entries(this.lifecycle)) { + if (value instanceof LifecyclePhase) { + value.before$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + value.after$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + } + } } async run() { @@ -59,7 +59,7 @@ export class FunctionalTestRunner { await providers.loadAll(); const mocha = await setupMocha(this.lifecycle, this.log, config, providers); - await this.lifecycle.trigger('beforeTests'); + await this.lifecycle.beforeTests.trigger(); this.log.info('Starting tests'); return await runTests(this.lifecycle, mocha); @@ -140,6 +140,6 @@ export class FunctionalTestRunner { if (this.closed) return; this.closed = true; - await this.lifecycle.trigger('cleanup'); + await this.lifecycle.cleanup.trigger(); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 88995e9acc5fe..2d354938d7648 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -17,7 +17,8 @@ * under the License. */ -export { createLifecycle, Lifecycle } from './lifecycle'; +export { Lifecycle } from './lifecycle'; +export { LifecyclePhase } from './lifecycle_phase'; export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection, Provider } from './providers'; export { runTests, setupMocha } from './mocha'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 2d9629a436b3a..7f78bc28c6d3d 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -17,64 +17,22 @@ * under the License. */ -import * as Rx from 'rxjs'; - -type Listener = (...args: any[]) => Promise | void; -export type Lifecycle = ReturnType; - -export function createLifecycle() { - const listeners = { - beforeLoadTests: [] as Listener[], - beforeTests: [] as Listener[], - beforeTestSuite: [] as Listener[], - beforeEachTest: [] as Listener[], - afterTestSuite: [] as Listener[], - testFailure: [] as Listener[], - testHookFailure: [] as Listener[], - cleanup: [] as Listener[], - phaseStart: [] as Listener[], - phaseEnd: [] as Listener[], - }; - - const cleanup$ = new Rx.ReplaySubject(1); - - return { - cleanup$: cleanup$.asObservable(), - - on(name: keyof typeof listeners, fn: Listener) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - listeners[name].push(fn); - return this; - }, - - async trigger(name: keyof typeof listeners, ...args: any[]) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - if (name === 'cleanup') { - if (cleanup$.closed) { - return; - } - - cleanup$.next(); - cleanup$.complete(); - } - - try { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseStart', name); - } - - await Promise.all(listeners[name].map(async fn => await fn(...args))); - } finally { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseEnd', name); - } - } - }, - }; +import { LifecyclePhase } from './lifecycle_phase'; + +// mocha's global types mean we can't import Mocha or it will override the global jest types.............. +type ItsASuite = any; +type ItsATest = any; + +export class Lifecycle { + public readonly beforeTests = new LifecyclePhase<[]>({ + singular: true, + }); + public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); + public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly testFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly testHookFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly cleanup = new LifecyclePhase<[]>({ + singular: true, + }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts new file mode 100644 index 0000000000000..94dd76884f2ca --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts @@ -0,0 +1,206 @@ +/* + * 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 Rx from 'rxjs'; +import { materialize, toArray } from 'rxjs/operators'; + +import { LifecyclePhase } from './lifecycle_phase'; + +describe('with randomness', () => { + beforeEach(() => { + const randomOrder = [0, 0.75, 0.5, 0.25, 1]; + jest.spyOn(Math, 'random').mockImplementation(() => { + const n = randomOrder.shift()!; + randomOrder.push(n); + return n; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('calls handlers in random order', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + phase.add( + jest.fn(() => { + order.push('one'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('two'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('three'); + }) + ); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "one", + "three", + "two", + ] + `); + }); +}); + +describe('without randomness', () => { + beforeEach(() => jest.spyOn(Math, 'random').mockImplementation(() => 0)); + afterEach(() => jest.restoreAllMocks()); + + it('calls all handlers and throws first error', async () => { + const phase = new LifecyclePhase(); + const fn1 = jest.fn(); + phase.add(fn1); + + const fn2 = jest.fn(() => { + throw new Error('foo'); + }); + phase.add(fn2); + + const fn3 = jest.fn(); + phase.add(fn3); + + await expect(phase.trigger()).rejects.toThrowErrorMatchingInlineSnapshot(`"foo"`); + expect(fn1).toHaveBeenCalled(); + expect(fn2).toHaveBeenCalled(); + expect(fn3).toHaveBeenCalled(); + }); + + it('triggers before$ just before calling handler and after$ once it resolves', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + const beforeSub = jest.fn(() => order.push('before')); + phase.before$.subscribe(beforeSub); + + const afterSub = jest.fn(() => order.push('after')); + phase.after$.subscribe(afterSub); + + const handler = jest.fn(async () => { + order.push('handler start'); + await new Promise(resolve => setTimeout(resolve, 100)); + order.push('handler done'); + }); + phase.add(handler); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "before", + "handler start", + "handler done", + "after", + ] + `); + }); + + it('completes before$ and after$ if phase is singular', async () => { + const phase = new LifecyclePhase({ singular: true }); + + const beforeNotifs: Array> = []; + phase.before$.pipe(materialize()).subscribe(n => beforeNotifs.push(n)); + + const afterNotifs: Array> = []; + phase.after$.pipe(materialize()).subscribe(n => afterNotifs.push(n)); + + await phase.trigger(); + expect(beforeNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + expect(afterNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('completes before$ subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.before$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('replays after$ event subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.after$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts new file mode 100644 index 0000000000000..5c7fdb532faa1 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts @@ -0,0 +1,82 @@ +/* + * 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 Rx from 'rxjs'; + +const shuffle = (arr: T[]) => arr.slice().sort(() => (Math.random() > 0.5 ? 1 : -1)); + +export type GetArgsType> = T extends LifecyclePhase + ? X + : never; + +export class LifecyclePhase { + private readonly handlers: Array<(...args: Args) => Promise | void> = []; + + private readonly beforeSubj = new Rx.Subject(); + public readonly before$ = this.beforeSubj.asObservable(); + + private readonly afterSubj = this.options.singular + ? new Rx.ReplaySubject(1) + : new Rx.Subject(); + public readonly after$ = this.afterSubj.asObservable(); + + constructor( + private readonly options: { + singular?: boolean; + } = {} + ) {} + + public add(fn: (...args: Args) => Promise | void) { + this.handlers.push(fn); + } + + public async trigger(...args: Args) { + if (this.beforeSubj.isStopped) { + throw new Error(`singular lifecycle event can only be triggered once`); + } + + this.beforeSubj.next(undefined); + if (this.options.singular) { + this.beforeSubj.complete(); + } + + // catch the first error but still execute all handlers + let error; + + // shuffle the handlers to prevent relying on their order + for (const fn of shuffle(this.handlers)) { + try { + await fn(...args); + } catch (_error) { + if (!error) { + error = _error; + } + } + } + + this.afterSubj.next(undefined); + if (this.options.singular) { + this.afterSubj.complete(); + } + + if (error) { + throw error; + } + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index e65eb2f27d063..4eb45229c2234 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -58,7 +58,7 @@ export function decorateMochaUi(lifecycle, context) { argumentsList[1] = function() { before(async () => { - await lifecycle.trigger('beforeTestSuite', this); + await lifecycle.beforeTestSuite.trigger(this); }); this.tags = tags => { @@ -68,7 +68,7 @@ export function decorateMochaUi(lifecycle, context) { provider.call(this); after(async () => { - await lifecycle.trigger('afterTestSuite', this); + await lifecycle.afterTestSuite.trigger(this); }); }; @@ -94,7 +94,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testFailure', err, test); + await lifecycle.testFailure.trigger(err, test); }) ); } @@ -112,7 +112,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testHookFailure', err, test); + await lifecycle.testHookFailure.trigger(err, test); }) ); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts index 2d98052b1062a..654f588fda858 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts @@ -35,7 +35,7 @@ export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { runComplete = true; }); - lifecycle.on('cleanup', () => { + lifecycle.cleanup.add(() => { if (!runComplete) runner.abort(); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index a425251a29f36..326877919d985 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -41,7 +41,7 @@ export async function setupMocha(lifecycle, log, config, providers) { // global beforeEach hook in root suite triggers before all others mocha.suite.beforeEach('global before each', async function() { - await lifecycle.trigger('beforeEachTest', this.currentTest); + await lifecycle.beforeEachTest.trigger(this.currentTest); }); loadTestFiles({ diff --git a/packages/kbn-ui-framework/dist/kui_dark.css b/packages/kbn-ui-framework/dist/kui_dark.css index dcbd65fbca520..aa16bcdb34d36 100644 --- a/packages/kbn-ui-framework/dist/kui_dark.css +++ b/packages/kbn-ui-framework/dist/kui_dark.css @@ -1,10 +1,24 @@ +/* 1 */ +/* 1 */ +/** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a - - button in the menu bar above to add a visualization to the dashboard. - -

-

- - visit the Visualize app - , + "maxWidth": "36em", } } > - If you haven't set up any visualizations yet, - - visit the Visualize app - - to create your first visualization - -

+
+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to add a visualization to the dashboard. +

+
+
+ +
+ + +
+

+ + visit the Visualize app + , + } + } + > + If you haven't set up any visualizations yet, + + visit the Visualize app + + to create your first visualization + +

+
+
+
+ + +
+ + + @@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to start working on your new dashboard. - -

+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to start working on your new dashboard. +

+
+
+
+ + + + +
+ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index a4604d17ddecd..69bdcf59bb227 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; describe('DashboardEmptyScreen', () => { const defaultProps = { @@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => { onLinkClick: jest.fn(), }; - function mountComponent(props?: Props) { + function mountComponent(props?: DashboardEmptyScreenProps) { const compProps = props || defaultProps; const comp = mountWithIntl(); return comp; @@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => { test('renders correctly with visualize paragraph', () => { const component = mountComponent(); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(1); }); test('renders correctly without visualize paragraph', () => { const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index 14c35759d70a9..d9eadf6c0e37d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,9 +6,5 @@ .dshStartScreen { text-align: center; - padding: $euiSize; - - > * { - max-width: 36em !important; - } + padding: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 42ecc0fea5f07..ef1bcab589c4a 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; @@ -134,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', - 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 4f41ab5d4fad6..3cf8932958b6d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -35,7 +35,7 @@ --> -
-
-

-
- -
- -
- -
- -
-
-

{{screenTitle}}

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..3b336ebfc11fe 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 @@ -21,9 +21,10 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; +import { uniq, noop } from 'lodash'; import { Subscription } from 'rxjs'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { subscribeWithScope, @@ -39,9 +40,7 @@ import { unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; -import { Query, SavedQuery, IndexPatterns } from '../../../../../plugins/data/public'; - -import './dashboard_empty_screen_directive'; +import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; import { DashboardContainer, @@ -78,7 +77,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; getAppState: any; globalState: State; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; dashboardConfig: any; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; @@ -143,6 +142,16 @@ export class DashboardAppController { } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + $scope.getShouldShowEditHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsEditMode() && + !dashboardConfig.getHideWriteControls(); + + $scope.getShouldShowViewHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsViewMode() && + !dashboardConfig.getHideWriteControls(); + const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { return; @@ -171,6 +180,17 @@ export class DashboardAppController { } }; + const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => { + const emptyScreenProps: DashboardEmptyScreenProps = { + onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, + showLinkToVisualize: shouldShowEditHelp, + }; + if (shouldShowEditHelp) { + emptyScreenProps.onVisualizeClick = noop; + } + return emptyScreenProps; + }; + const getDashboardInput = (): DashboardContainerInput => { const embeddablesMap: { [key: string]: DashboardPanelState; @@ -182,6 +202,8 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; } + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); return { id: dashboardStateManager.savedDashboard.id || '', filters: queryFilter.getFilters(), @@ -194,6 +216,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmptyState: shouldShowEditHelp || shouldShowViewHelp, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, title: dashboardStateManager.getTitle(), @@ -234,6 +257,15 @@ export class DashboardAppController { if (!isErrorEmbeddable(container)) { dashboardContainer = container; + dashboardContainer.renderEmpty = () => { + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); + const isEmptyState = shouldShowEditHelp || shouldShowViewHelp; + return isEmptyState ? ( + + ) : null; + }; + updateIndexPatterns(dashboardContainer); outputSubscription = dashboardContainer.getOutput$().subscribe(() => { @@ -334,15 +366,6 @@ export class DashboardAppController { updateBreadcrumbs(); dashboardStateManager.registerChangeListener(updateBreadcrumbs); - $scope.getShouldShowEditHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsEditMode() && - !dashboardConfig.getHideWriteControls(); - $scope.getShouldShowViewHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsViewMode() && - !dashboardConfig.getHideWriteControls(); - const getChangesFromAppStateForContainerState = () => { const appStateDashboardInput = getDashboardInput(); if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { @@ -729,6 +752,8 @@ export class DashboardAppController { } }; + navActions[TopNavIds.VISUALIZE] = async () => {}; + navActions[TopNavIds.OPTIONS] = anchorElement => { showOptionsPopover({ anchorElement, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx index d5a4e6e6a325d..234228ba4166a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -18,29 +18,43 @@ */ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiSpacer, + EuiPageContent, + EuiPageBody, + EuiPage, + EuiText, +} from '@elastic/eui'; import * as constants from './dashboard_empty_screen_constants'; -export interface Props { +export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; + onVisualizeClick?: () => void; } -export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { +export function DashboardEmptyScreen({ + showLinkToVisualize, + onLinkClick, +}: DashboardEmptyScreenProps) { const linkToVisualizeParagraph = ( -

- - {constants.visualizeAppLinkTest} - - ), - }} - /> -

+ +

+ + {constants.visualizeAppLinkTest} + + ), + }} + /> +

+
); const paragraph = ( description1: string, @@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props dataTestSubj?: string ) => { return ( -

- + +

{description1} {linkText} {description2} - -

+

+ ); }; const addVisualizationParagraph = ( @@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props constants.addVisualizationLinkAriaLabel, 'emptyDashboardAddPanelButton' )} + {linkToVisualizeParagraph} ); @@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props ); return ( - - -

{constants.fillDashboardTitle}

- {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
+ + + + + + +

{constants.fillDashboardTitle}

+
+ + {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
+
+
); } 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/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index 7c3c389330887..b0f09f0cf9745 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -63,6 +63,6 @@ export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; 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/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts index 9df86f2ca3cce..c67d6891c18e7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts @@ -26,4 +26,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + VISUALIZE: 'visualize', }; diff --git a/src/legacy/core_plugins/kibana/public/discover/_discover.scss b/src/legacy/core_plugins/kibana/public/discover/_discover.scss index 12cac1c89275b..0da28e41579ae 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_discover.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_discover.scss @@ -1,5 +1,3 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - discover-app { flex-grow: 1; @@ -37,7 +35,7 @@ discover-app { z-index: 1; } -@include euiPanel('dscWrapper__content'); +@include euiPanel('.dscWrapper__content'); .dscWrapper__content { padding-top: $euiSizeXS; @@ -213,7 +211,6 @@ discover-app { } .dscResults { - h3 { margin: -20px 0 10px 0; text-align: center; 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..ce1419cd637b9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -65,7 +65,6 @@ const { data, docTitle, filterManager, - State, share, timefilter, toastNotifications, @@ -73,13 +72,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', @@ -122,10 +120,10 @@ app.config($routeProvider => { template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { - const indexPatterns = dataLP.indexPatterns.indexPatterns; + savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope, State) { + 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) => { /** @@ -138,7 +136,6 @@ app.config($routeProvider => { * @type {State} */ const state = new State('_a', {}); - const id = getIndexPatternId(state.index, indexPatternList, uiSettings.get('defaultIndex')); state.destroy(); return Promise.props({ @@ -422,7 +419,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 +575,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 +585,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 +630,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 +701,7 @@ function discoverController( savedSearchTitle: savedSearch.title, } }), - 'dataLP-test-subj': 'saveSearchSuccess', + 'data-test-subj': 'saveSearchSuccess', }); if (savedSearch.id !== $route.current.params.id) { @@ -765,7 +762,7 @@ function discoverController( } else { toastNotifications.addError(error, { title: i18n.translate('kbn.discover.errorLoadingData', { - defaultMessage: 'Error loading dataLP', + defaultMessage: 'Error loading data', }), }); } @@ -813,10 +810,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 +969,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..f6982e13d7d03 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 @@ -25,6 +25,8 @@ import 'ui/angular-bootstrap'; import { IPrivate } from 'ui/private'; import { EuiIcon } from '@elastic/eui'; // @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore import { EventsProvider } from 'ui/events'; import { PersistedState } from 'ui/persisted_state'; // @ts-ignore @@ -277,6 +279,9 @@ function createLocalAppStateModule() { }) .service('getAppState', function(Private: any) { return Private(AppStateProvider).getAppState; + }) + .service('State', function(Private: any) { + return Private(StateProvider); }); } @@ -301,7 +306,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..b72bd27a31cf9 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,16 +27,12 @@ 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'; -// @ts-ignore -import { StateProvider } from 'ui/state_management/state'; +import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; // @ts-ignore import { createSavedSearchesService } from '../saved_searches/saved_searches'; // @ts-ignore import { createSavedSearchFactory } from '../saved_searches/_saved_search'; import { DiscoverStartPlugins } from '../plugin'; -import { start as legacyData } from '../../../../data/public/legacy'; -import { DataStart, IndexPatterns } from '../../../../data/public'; import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; import { SavedSearch } from '../types'; import { SharePluginStart } from '../../../../../../plugins/share/public'; @@ -46,12 +42,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; @@ -60,7 +55,6 @@ export interface DiscoverServices { // legacy getSavedSearchById: (id: string) => Promise; getSavedSearchUrlById: (id: string) => Promise; - State: unknown; uiSettings: IUiSettingsClient; } @@ -68,13 +62,11 @@ export async function buildGlobalAngularServices() { const injector = await chromeLegacy.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); const kbnUrl = injector.get('kbnUrl'); - const State = Private(StateProvider); const SavedSearchFactory = createSavedSearchFactory(Private); const service = createSavedSearchesService(Private, SavedSearchFactory, kbnUrl, chromeLegacy); return { getSavedSearchById: async (id: string) => service.get(id), getSavedSearchUrlById: async (id: string) => service.urlFor(id), - State, }; } @@ -98,7 +90,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 0d9dab96d6120..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'; @@ -78,11 +77,16 @@ export { tabifyAggResponse } from 'ui/agg_response/tabify'; // @ts-ignore export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from 'ui/state_management/state_hashing'; +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/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 7a6a3ca1d01d0..611fe613ad99c 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -1,8 +1,6 @@ @import 'src/legacy/ui/public/styles/styling_constants'; // Elastic charts -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; @import '@elastic/charts/dist/theme'; @import '@elastic/eui/src/themes/charts/theme'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index cc438d338c7d5..0045819257018 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -29,7 +29,6 @@ import { npSetup } from 'ui/new_platform'; import 'uiExports/home'; import 'uiExports/visTypes'; -import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; import 'uiExports/fieldFormatEditors'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js index 5cb93a36cdd18..1e894664c2bf1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js @@ -50,8 +50,6 @@ export class StepTimeField extends Component { constructor(props) { super(props); - const { getIndexPatternType, getIndexPatternName } = props.indexPatternCreationType; - this.state = { error: '', timeFields: [], @@ -61,8 +59,8 @@ export class StepTimeField extends Component { isFetchingTimeFields: false, isCreating: false, indexPatternId: '', - indexPatternType: getIndexPatternType(), - indexPatternName: getIndexPatternName(), + indexPatternType: props.indexPatternCreationType.getIndexPatternType(), + indexPatternName: props.indexPatternCreationType.getIndexPatternName(), }; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index b500f5c79e98b..833ca8467292e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -21,7 +21,7 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import uiRoutes from 'ui/routes'; import angularTemplate from './angular_template.html'; import 'ui/index_patterns'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render'; @@ -35,8 +35,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const Private = $injector.get('Private'); $scope.$$postDigest(() => { const $routeParams = $injector.get('$routeParams'); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)($routeParams.type); - const indexPatternCreationType = indexPatternCreationProvider.getType(); + const indexPatternCreationType = managementSetup.indexPattern.creation.getType( + $routeParams.type + ); const services = { config: $injector.get('config'), es: $injector.get('es'), @@ -52,12 +53,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined; - renderCreateIndexPatternWizard( - initialQuery, - services - ); + renderCreateIndexPatternWizard(initialQuery, services); }); $scope.$on('$destroy', destroyCreateIndexPatternWizard); - } + }, }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 6ae84b9c641c2..150fae6e87dde 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -28,7 +28,7 @@ import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; import template from './edit_index_pattern.html'; import { fieldWildcardMatcher } from 'ui/field_wildcard'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SourceFiltersTable } from './source_filters_table'; @@ -58,13 +58,17 @@ function updateSourceFiltersTable($scope, $state) { filterFilter={$scope.fieldFilter} fieldWildcardMatcher={$scope.fieldWildcardMatcher} onAddOrRemoveFilter={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -77,7 +81,6 @@ function destroySourceFiltersTable() { node && unmountComponentAtNode(node); } - function updateScriptedFieldsTable($scope, $state) { if ($state.tab === 'scriptedFields') { $scope.$$postDigest(() => { @@ -100,13 +103,17 @@ function updateScriptedFieldsTable($scope, $state) { getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route), }} onRemoveField={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -144,7 +151,7 @@ function updateIndexedFieldsTable($scope, $state) { }} /> , - node, + node ); }); } else { @@ -157,34 +164,36 @@ function destroyIndexedFieldsTable() { node && unmountComponentAtNode(node); } -uiRoutes - .when('/management/kibana/index_patterns/:indexPatternId', { - template, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { - return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)) - .catch(redirectWhenMissing('/management/kibana/index_patterns')); - } +uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', { + template, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { + return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( + redirectWhenMissing('/management/kibana/index_patterns') + ); }, - }); + }, +}); -uiModules.get('apps/management') +uiModules + .get('apps/management') .controller('managementIndexPatternsEdit', function ( $scope, $location, $route, Promise, config, indexPatterns, Private, AppState, confirmModal) { const $state = $scope.state = new AppState(); - const indexPatternListProvider = Private(IndexPatternListFactory)(); $scope.fieldWildcardMatcher = (...args) => fieldWildcardMatcher(...args, config.get('metaFields')); $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = indexPatternListProvider; - $scope.indexPattern.tags = indexPatternListProvider.getIndexPatternTags( + $scope.indexPatternListProvider = managementSetup.indexPattern.list; + $scope.indexPattern.tags = managementSetup.indexPattern.list.getIndexPatternTags( $scope.indexPattern, $scope.indexPattern.id === config.get('defaultIndex') ); - $scope.getFieldInfo = indexPatternListProvider.getFieldInfo; + $scope.getFieldInfo = managementSetup.indexPattern.list.getFieldInfo.bind( + managementSetup.indexPattern.list + ); docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { @@ -192,7 +201,11 @@ uiModules.get('apps/management') }); $scope.$watch('indexPattern.fields', function () { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); $scope.refreshFilters(); $scope.fields = $scope.indexPattern.getNonScriptedFields(); updateIndexedFieldsTable($scope, $state); @@ -231,26 +244,26 @@ uiModules.get('apps/management') }); $scope.$watchCollection('indexPattern.fields', function () { - $scope.conflictFields = $scope.indexPattern.fields - .filter(field => field.type === 'conflict'); + $scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict'); }); $scope.refreshFields = function () { const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', { - defaultMessage: 'This action resets the popularity counter of each field.' + defaultMessage: 'This action resets the popularity counter of each field.', }); const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { defaultMessage: 'Refresh' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { + defaultMessage: 'Refresh', + }), onConfirm: async () => { await $scope.indexPattern.init(true); $scope.fields = $scope.indexPattern.getNonScriptedFields(); }, - title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { defaultMessage: 'Refresh field list?' }) + title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { + defaultMessage: 'Refresh field list?', + }), }; - confirmModal( - confirmMessage, - confirmModalOptions - ); + confirmModal(confirmMessage, confirmModalOptions); }; $scope.removePattern = function () { @@ -271,9 +284,13 @@ uiModules.get('apps/management') } const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { defaultMessage: 'Delete' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { + defaultMessage: 'Delete', + }), onConfirm: doRemove, - title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { defaultMessage: 'Delete index pattern?' }) + title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { + defaultMessage: 'Delete index pattern?', + }), }; confirmModal('', confirmModalOptions); }; @@ -285,7 +302,8 @@ uiModules.get('apps/management') $scope.setIndexPatternsTimeField = function (field) { if (field.type !== 'date') { const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', { - defaultMessage: 'That field is a {fieldType} not a date.', values: { fieldType: field.type } + defaultMessage: 'That field is a {fieldType} not a date.', + values: { fieldType: field.type }, }); toastNotifications.addDanger(errorMessage); return; @@ -295,12 +313,16 @@ uiModules.get('apps/management') }; $scope.$watch('fieldFilter', () => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); if ($scope.fieldFilter === undefined) { return; } - switch($state.tab) { + switch ($state.tab) { case 'indexedFields': updateIndexedFieldsTable($scope, $state); case 'scriptedFields': diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 272544fa036cc..5935afec1dd70 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,8 +18,7 @@ */ import { management } from 'ui/management'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../management/public/legacy'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -28,7 +27,10 @@ import indexTemplate from './index.html'; import indexPatternListTemplate from './list.html'; import { IndexPatternTable } from './index_pattern_table'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { UICapabilitiesProvider } from 'ui/capabilities/react'; @@ -39,11 +41,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; const INDEX_PATTERN_LIST_DOM_ELEMENT_ID = 'indexPatternListReact'; -export function updateIndexPatternList( - indexPatterns, - kbnUrl, - indexPatternCreationOptions, -) { +export function updateIndexPatternList(indexPatterns, kbnUrl, indexPatternCreationOptions) { const node = document.getElementById(INDEX_PATTERN_LIST_DOM_ELEMENT_ID); if (!node) { return; @@ -59,7 +57,7 @@ export function updateIndexPatternList( /> , - node, + node ); } @@ -72,55 +70,56 @@ const indexPatternsResolutions = { indexPatterns: function (Private) { const savedObjectsClient = Private(SavedObjectsClientProvider); - return savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage: 10000 - }).then(response => response.savedObjects); - } + return savedObjectsClient + .find({ + type: 'index-pattern', + fields: ['title', 'type'], + perPage: 10000, + }) + .then(response => response.savedObjects); + }, }; // add a dependency to all of the subsection routes -uiRoutes - .defaults(/management\/kibana\/(index_patterns|index_pattern)/, { - resolve: indexPatternsResolutions, - requireUICapability: 'management.kibana.index_patterns', - badge: uiCapabilities => { - if (uiCapabilities.indexPatterns.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save index patterns', - }), - iconType: 'glasses' - }; +uiRoutes.defaults(/management\/kibana\/(index_patterns|index_pattern)/, { + resolve: indexPatternsResolutions, + requireUICapability: 'management.kibana.index_patterns', + badge: uiCapabilities => { + if (uiCapabilities.indexPatterns.save) { + return undefined; } - }); -uiRoutes - .when('/management/kibana/index_patterns', { - template: indexPatternListTemplate, - k7Breadcrumbs: getListBreadcrumbs - }); + return { + text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save index patterns', + }), + iconType: 'glasses', + }; + }, +}); + +uiRoutes.when('/management/kibana/index_patterns', { + template: indexPatternListTemplate, + k7Breadcrumbs: getListBreadcrumbs, +}); // wrapper directive, which sets some global stuff up like the left nav -uiModules.get('apps/management') - .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl, Private) { +uiModules + .get('apps/management') + .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl) { return { restrict: 'E', transclude: true, template: indexTemplate, link: async function ($scope) { - const indexPatternListProvider = Private(IndexPatternListFactory)(); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)(); - const indexPatternCreationOptions = await indexPatternCreationProvider.getIndexPatternCreationOptions((url) => { - $scope.$evalAsync(() => kbnUrl.change(url)); - }); + const indexPatternCreationOptions = await managementSetup.indexPattern.creation.getIndexPatternCreationOptions( + url => { + $scope.$evalAsync(() => kbnUrl.change(url)); + } + ); const renderList = () => { $scope.indexPatternList = @@ -129,7 +128,7 @@ uiModules.get('apps/management') const id = pattern.id; const title = pattern.get('title'); const isDefault = $scope.defaultIndex === id; - const tags = indexPatternListProvider.getIndexPatternTags( + const tags = managementSetup.indexPattern.list.getIndexPatternTags( pattern, isDefault ); @@ -165,25 +164,30 @@ uiModules.get('apps/management') $scope.$watch('defaultIndex', () => renderList()); config.bindToScope($scope, 'defaultIndex'); $scope.$apply(); - } + }, }; }); management.getSection('kibana').register('index_patterns', { - display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns' }), + display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { + defaultMessage: 'Index Patterns', + }), order: 0, - url: '#/management/kibana/index_patterns/' + url: '#/management/kibana/index_patterns/', }); FeatureCatalogueRegistryProvider.register(() => { return { id: 'index_patterns', - title: i18n.translate('kbn.management.indexPatternHeader', { defaultMessage: 'Index Patterns' }), - description: i18n.translate('kbn.management.indexPatternLabel', - { defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.' }), + title: i18n.translate('kbn.management.indexPatternHeader', { + defaultMessage: 'Index Patterns', + }), + description: i18n.translate('kbn.management.indexPatternLabel', { + defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.', + }), icon: 'indexPatternApp', path: '/app/kibana#/management/kibana/index_patterns', showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN + category: FeatureCatalogueCategory.ADMIN, }; }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index 5956b6c306b0e..1c3666ac0980c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,6 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../management/public/np_ready/mocks'; import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; @@ -29,6 +30,11 @@ import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); +jest.mock('../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('../../../lib/find_objects', () => ({ findObjects: jest.fn(), })); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index e465149b301dc..97c0d5b89d657 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; import { Flyout } from '../flyout'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -48,6 +48,11 @@ jest.mock('../../../../../lib/resolve_saved_objects', () => ({ saveObjects: jest.fn(), })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('ui/notify', () => ({})); const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js index d3f5fb1945254..fba249670ce60 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js @@ -21,6 +21,7 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { keyCodes } from '@elastic/eui/lib/services'; +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -28,6 +29,11 @@ jest.mock('ui/chrome', () => ({ addBasePath: () => '', })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + import { Table } from '../table'; const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js index 43cf8c2a23286..eeddc390037a6 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js @@ -18,7 +18,7 @@ */ import chrome from 'ui/chrome'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; +import { setup as managementSetup } from '../../../../../../../../../management/public/legacy'; import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; @@ -79,7 +79,7 @@ export class Table extends PureComponent { constructor(props) { super(props); - this.extraActions = SavedObjectsManagementActionRegistry.get(); + this.extraActions = managementSetup.savedObjects.registry.get(); } onChange = ({ query, error }) => { 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/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts new file mode 100644 index 0000000000000..7684f982de7e0 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -0,0 +1,193 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular, { IModule } from 'angular'; +import { EuiConfirmModal } from '@elastic/eui'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; + +import { AppMountContext, LegacyCoreStart } from 'kibana/public'; +import { + AppStateProvider, + AppState, + configureAppAngularModule, + confirmModalFactory, + createTopNavDirective, + createTopNavHelper, + EventsProvider, + GlobalStateProvider, + KbnUrlProvider, + RedirectWhenMissingProvider, + IPrivate, + PersistedState, + PrivateProvider, + PromiseServiceCreator, + StateManagementConfigProvider, +} from './legacy_imports'; +import { NavigationStart } from '../../../navigation/public'; + +// @ts-ignore +import { initVisualizeApp } from './legacy_app'; +import { VisualizeKibanaServices } from './kibana_services'; + +let angularModuleInstance: IModule | null = null; + +export const renderApp = async ( + element: HTMLElement, + appBasePath: string, + deps: VisualizeKibanaServices +) => { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); + // custom routing stuff + initVisualizeApp(angularModuleInstance, deps); + } + const $injector = mountVisualizeApp(appBasePath, element); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/visualize'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountVisualizeApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); + + const visualizeAngularModule: IModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/visualize/Config', + 'app/visualize/I18n', + 'app/visualize/Private', + 'app/visualize/PersistedState', + 'app/visualize/TopNav', + 'app/visualize/State', + 'app/visualize/ConfirmModal', + ]); + return visualizeAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('app/visualize/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule() { + angular + .module('app/visualize/State', [ + 'app/visualize/Private', + 'app/visualize/Config', + 'app/visualize/KbnUrl', + 'app/visualize/Promise', + 'app/visualize/PersistedState', + ]) + .factory('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: IPrivate) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('app/visualize/Config', ['app/visualize/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('app/visualize/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('app/visualize/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html index bf9ac9b9bbe36..6190b92c9be3e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -39,7 +39,7 @@ show-search-bar="true" show-query-bar="true" show-query-input="showQueryInput()" - show-filter-bar="showFilterBar() && chrome.getVisible()" + show-filter-bar="showFilterBar() && isVisible" show-date-picker="showQueryBarTimePicker()" show-auto-refresh-only="!showQueryBarTimePicker()" query="state.query" @@ -67,7 +67,7 @@ --> savedVisualizations.get($route.current.params)) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); - } - return savedVis; - }) - .catch(redirectWhenMissing({ - '*': '/visualize' - })); - } - } - }) - .when(`${VisualizeConstants.EDIT_PATH}/:id`, { - template: editorTemplate, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) - .then(() => savedVisualizations.get($route.current.params.id)) - .then((savedVis) => { - chrome.recentlyAccessed.add( - savedVis.getFullPath(), - savedVis.title, - savedVis.id - ); - return savedVis; - }) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis).catch(() => savedVis); - } - return savedVis; - }) - .catch( - redirectWhenMissing({ - visualization: '/visualize', - search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - }) - ); - } - } - }); +import { getServices } from '../kibana_services'; -uiModules - .get('app/visualize', [ - 'kibana/url' - ]) - .directive('visualizeApp', function () { +export function initEditorDirective(app, deps) { + app.directive('visualizeApp', function () { return { restrict: 'E', controllerAs: 'visualizeApp', - controller: VisEditor, + controller: VisualizeAppController, }; }); -function VisEditor( + initVisEditorDirective(app, deps); + initVisualizationDirective(app, deps); +} + +function VisualizeAppController( $scope, $element, $route, @@ -155,19 +70,42 @@ function VisEditor( $window, $injector, $timeout, - indexPatterns, kbnUrl, redirectWhenMissing, - Private, Promise, - config, kbnBaseUrl, - localStorage, + getAppState, + globalState, ) { - const queryFilter = Private(FilterBarQueryFilterProvider); - + const { + indexPatterns, + localStorage, + visualizeCapabilities, + share, + data: { + query: { + filterManager, + timefilter: { timefilter }, + }, + }, + toastNotifications, + legacyChrome, + chrome, + getBasePath, + core: { docLinks }, + savedQueryService, + uiSettings, + } = getServices(); + + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; + const _applyVis = () => { + $scope.$apply(); + }; + // This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js. + savedVis.vis.on('apply', _applyVis); // vis is instance of src/legacy/ui/public/vis/vis.js. // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { vis, searchSource } = savedVis; @@ -178,7 +116,7 @@ function VisEditor( dirty: !savedVis.id }; - $scope.topNavMenu = [...(capabilities.visualize.save ? [{ + $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ id: 'save', label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { @@ -247,7 +185,7 @@ function VisEditor( share.toggleShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: capabilities.visualize.createShortUrl, + allowShortUrl: visualizeCapabilities.createShortUrl, shareableUrl: unhashUrl(window.location.href), objectId: savedVis.id, objectType: 'visualization', @@ -296,7 +234,7 @@ function VisEditor( let stateMonitor; if (savedVis.id) { - docTitle.change(savedVis.title); + chrome.docTitle.change(savedVis.title); } // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. @@ -307,7 +245,7 @@ function VisEditor( linked: !!savedVis.savedSearchId, query: searchSource.getOwnField('query') || { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }, filters: searchSource.getOwnField('filter') || [], vis: savedVisState @@ -346,9 +284,9 @@ function VisEditor( queryFilter.setFilters(filters); }; - $scope.showSaveQuery = capabilities.visualize.saveQuery; + $scope.showSaveQuery = visualizeCapabilities.saveQuery; - $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { + $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { $scope.showSaveQuery = newCapability; }); @@ -456,13 +394,15 @@ function VisEditor( } })); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { if ($scope._handler) { $scope._handler.destroy(); } savedVis.destroy(); stateMonitor.destroy(); + filterStateManager.destroy(); subscriptions.unsubscribe(); + $scope.vis.off('apply', _applyVis); }); @@ -504,7 +444,7 @@ function VisEditor( delete $state.savedQuery; $state.query = { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }; queryFilter.removeAll(); $state.save(); @@ -590,14 +530,14 @@ function VisEditor( // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved // url, not the unsaved one. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + legacyChrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); kbnUrl.change(dashboardParsedUrl.appPath); } else if (savedVis.id === $route.current.params.id) { - docTitle.change(savedVis.lastSavedTitle); + chrome.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); savedVis.vis.title = savedVis.title; savedVis.vis.description = savedVis.description; @@ -662,7 +602,7 @@ function VisEditor( vis.type.feedbackMessage; }; - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); init(); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js index 198fbe19a0b7a..d3651735c1a1d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -17,13 +17,8 @@ * under the License. */ -import { getServices } from '../kibana_services'; - -const { embeddables, uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEmbedded', function (Private, $timeout, getAppState) { +export function initVisualizationDirective(app, deps) { + app.directive('visualizationEmbedded', function ($timeout, getAppState) { return { restrict: 'E', @@ -37,7 +32,7 @@ uiModules link: function ($scope, element) { $scope.renderFunction = async () => { if (!$scope._handler) { - $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { + $scope._handler = await deps.embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { timeRange: $scope.timeRange, filters: $scope.filters || [], query: $scope.query, @@ -66,3 +61,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index ead77e6bc41d5..bc6d4d4c48466 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,15 +17,8 @@ * under the License. */ -import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; - -const { uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEditor', function (Private, $timeout, getAppState) { - const editorTypes = Private(VisEditorTypesRegistryProvider); - +export function initVisEditorDirective(app, deps) { + app.directive('visualizationEditor', function ($timeout, getAppState) { return { restrict: 'E', scope: { @@ -38,7 +31,8 @@ uiModules link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; const Editor = typeof editorType === 'function' ? editorType : - editorTypes.find(editor => editor.key === editorType); + deps.editorTypes.find(editor => editor.key === editorType); + const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { @@ -62,3 +56,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index 065feae045597..d8792a761b186 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,8 +19,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; -import { Embeddable, EmbeddableOutput } from '../kibana_services'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 9bc9ab99c4aff..7fe3678bb1f77 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,11 +17,10 @@ * under the License. */ -import { getServices, getFromSavedObject, VisSavedObject } from '../kibana_services'; +import { npStart } from 'ui/new_platform'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; - -const { savedObjectsClient, uiSettings } = getServices(); +import { VisSavedObject } from './visualize_embeddable'; +import { indexPatterns, IIndexPattern } from '../../../../../../plugins/data/public'; export async function getIndexPattern( savedVis: VisSavedObject @@ -30,7 +29,8 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const defaultIndex = uiSettings.get('defaultIndex'); + const savedObjectsClient = npStart.core.savedObjects.client; + const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ @@ -39,10 +39,10 @@ export async function getIndexPattern( search: `"${savedVis.vis.params.index_pattern}"`, searchFields: ['title'], }); - const [indexPattern] = indexPatternObjects.savedObjects.map(getFromSavedObject); + const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject); return indexPattern; } const savedObject = await savedObjectsClient.get('index-pattern', defaultIndex); - return getFromSavedObject(savedObject); + return indexPatterns.getFromSavedObject(savedObject); } 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..7ab60f8867c38 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 { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 15ad9a33232ef..7c9efa280c9f1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,36 +17,48 @@ * under the License. */ +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; + import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; + import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; +import { + EmbeddableFactory, + ErrorEmbeddable, + Container, + EmbeddableOutput, +} from '../../../../../../plugins/embeddable/public'; +import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; -import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; - import { - getServices, - Container, - EmbeddableFactory, - EmbeddableOutput, - ErrorEmbeddable, + VisualizeEmbeddable, + VisualizeInput, + VisualizeOutput, VisSavedObject, -} from '../kibana_services'; - -const { - addBasePath, - capabilities, - embeddable, - getInjector, - uiSettings, - visualizations, -} = getServices(); +} from './visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -96,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (uiSettings.get('visualize:enableLabs')) { + if (npStart.core.uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -108,7 +120,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.visualize.save as boolean; + return npStart.core.application.capabilities.visualize.save as boolean; } public getDisplayName() { @@ -122,14 +134,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; - const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; + const editUrl = visId + ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) + : ''; const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { @@ -161,7 +175,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const savedVisualizations = $injector.get('savedVisualizations'); try { @@ -179,14 +193,15 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. if (this.visTypes) { - showNewVisModal(this.visTypes, { - editorParams: ['addToDashboard'], - }); + showNewVisModal( + this.visTypes, + { + editorParams: ['addToDashboard'], + }, + npStart.core.http.basePath.prepend, + npStart.core.uiSettings + ); } return undefined; } } - -VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts new file mode 100644 index 0000000000000..71156bc38d498 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -0,0 +1,67 @@ +/* + * 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 { State } from './legacy_imports'; +import { DataPublicPluginStart as DataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: DataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index d27003f39d4c0..9c00947d7663c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -18,10 +18,8 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; -const { docLinks } = getServices(); -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension({ appName: i18n.translate('kbn.visualize.helpMenu.appName', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js deleted file mode 100644 index 57707f6321376..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ /dev/null @@ -1,89 +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 { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -import './editor/editor'; -import { i18n } from '@kbn/i18n'; -import './saved_visualizations/_saved_vis'; -import './saved_visualizations/saved_visualizations'; -import visualizeListingTemplate from './listing/visualize_listing.html'; -import { VisualizeListingController } from './listing/visualize_listing'; -import { VisualizeConstants } from './visualize_constants'; -import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; - -import { getServices, FeatureCatalogueCategory } from './kibana_services'; - -const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); - -uiRoutes - .defaults(/visualize/, { - requireUICapability: 'visualize.show', - badge: uiCapabilities => { - if (uiCapabilities.visualize.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses' - }; - } - }) - .when(VisualizeConstants.LANDING_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate( - 'kbn.visualize.visualizeDescription', - { - defaultMessage: 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - } - ), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts new file mode 100644 index 0000000000000..5e9f2fdeb8999 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -0,0 +1,70 @@ +/* + * 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 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; + +import { + IPrivate, + legacyChrome, + npSetup, + npStart, + SavedObjectRegistryProvider, + VisEditorTypesRegistryProvider, +} from './legacy_imports'; +import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const editorTypes = Private(VisEditorTypesRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + + return { + legacyChrome, + editorTypes, + savedObjectRegistry, + savedVisualizations: injector.get('savedVisualizations'), + }; +} + +(() => { + const instance = new VisualizePlugin(); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + embeddables, + navigation, + visualizations, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 6477d1941c205..36a9ecf3fcf8c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,108 +17,59 @@ * under the License. */ -import 'angular-sanitize'; // used in visualization_editor.js and visualization.js -import 'ui/collapsible_sidebar'; // used in default editor -import 'ui/vis/editors/default/sidebar'; -// load directives -import '../../../data/public'; +import { + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + ToastsStart, + IUiSettingsClient, +} from 'kibana/public'; -import { npStart } from 'ui/new_platform'; -import angular from 'angular'; // just used in editor.js -import chromeLegacy from 'ui/chrome'; +import { NavigationStart } from '../../../navigation/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plugins/data/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { SavedVisualizations } from './types'; -import uiRoutes from 'ui/routes'; - -// @ts-ignore -import { docTitle } from 'ui/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; - -// Saved objects -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -// @ts-ignore -import { SavedObject, SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; - -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; -import { start as data } from '../../../data/public/legacy'; - -const services = { - // new platform - addBasePath: npStart.core.http.basePath.prepend, - capabilities: npStart.core.application.capabilities, - chrome: npStart.core.chrome, - docLinks: npStart.core.docLinks, - embeddable: npStart.plugins.embeddable, - getBasePath: npStart.core.http.basePath.get, - savedObjectsClient: npStart.core.savedObjects.client, - toastNotifications: npStart.core.notifications.toasts, - uiSettings: npStart.core.uiSettings, - core: npStart.core, - - share: npStart.plugins.share, - npData: npStart.plugins.data, - data, - embeddables, - visualizations, - - // legacy - chromeLegacy, - docTitle, - FeatureCatalogueRegistryProvider, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectProvider, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter: npStart.plugins.data.query.timefilter.timefilter, - uiModules, - uiRoutes, - wrapInI18nContext, +export interface VisualizeKibanaServices { + addBasePath: (url: string) => string; + chrome: ChromeStart; + core: LegacyCoreStart; + data: DataPublicPluginStart; + editorTypes: any; + embeddables: IEmbeddableStart; + getBasePath: () => string; + indexPatterns: IndexPatternsContract; + legacyChrome: any; + localStorage: Storage; + navigation: NavigationStart; + toastNotifications: ToastsStart; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + savedQueryService: DataPublicPluginStart['query']['savedQueries']; + savedVisualizations: SavedVisualizations; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + visualizeCapabilities: any; + visualizations: VisualizationsStart; +} - createUiStatsReporter, -}; +let services: VisualizeKibanaServices | null = null; +export function setServices(newServices: VisualizeKibanaServices) { + services = newServices; +} export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the visualize app?' + ); + } return services; } -// export legacy static dependencies -export { angular }; -export { getFromSavedObject } from 'ui/index_patterns'; -export { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; -export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; -export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; -export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { unhashUrl } from 'ui/state_management/state_hashing'; -export { - Container, - Embeddable, - EmbeddableFactory, - EmbeddableInput, - EmbeddableOutput, - ErrorEmbeddable, -} from '../../../../../plugins/embeddable/public'; - -// export types -export { METRIC_TYPE }; -export { AppState } from 'ui/state_management/app_state'; -export { VisType } from 'ui/vis'; - -// export const -export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; - -export { VisSavedObject } from './embeddable/visualize_embeddable'; +export function clearServices() { + services = null; +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js new file mode 100644 index 0000000000000..f47552e99a5c7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -0,0 +1,169 @@ +/* + * 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 { find } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import editorTemplate from './editor/editor.html'; +import visualizeListingTemplate from './listing/visualize_listing.html'; + +import { initVisualizeAppDirective } from './visualize_app'; +import { VisualizeConstants } from './visualize_constants'; +import { VisualizeListingController } from './listing/visualize_listing'; +import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; +import { syncOnMount } from './global_state_sync'; + +import { + getLandingBreadcrumbs, + getWizardStep1Breadcrumbs, + getCreateBreadcrumbs, + getEditBreadcrumbs +} from './breadcrumbs'; + +export function initVisualizeApp(app, deps) { + initVisualizeAppDirective(app, deps); + + app.run(globalState => { + syncOnMount(globalState, deps.data); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.data.query.timefilter.timefilter, + globalState, + $rootScope + ); + }); + + app.config(function ($routeProvider) { + const defaults = { + reloadOnSearch: false, + requireUICapability: 'visualize.show', + badge: () => { + if (deps.visualizeCapabilities.save) { + return undefined; + } + + return { + text: i18n.translate('kbn.visualize.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save visualizations', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(VisualizeConstants.LANDING_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getLandingBreadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getWizardStep1Breadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.CREATE_PATH, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getCreateBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { core, data, savedVisualizations, visualizations } = deps; + const visTypes = visualizations.types.all(); + const visType = find(visTypes, { name: $route.current.params.type }); + const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; + const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId; + if (shouldHaveIndex && !hasIndex) { + throw new Error( + i18n.translate('kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage', { + defaultMessage: 'You must provide either an indexPattern or a savedSearchId', + }) + ); + } + + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) + .catch(redirectWhenMissing({ + '*': '/visualize' + })); + } + } + }) + .when(`${VisualizeConstants.EDIT_PATH}/:id`, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { chrome, core, data, savedVisualizations } = deps; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) + .then(savedVis => { + chrome.recentlyAccessed.add( + savedVis.getFullPath(), + savedVis.title, + savedVis.id + ); + return savedVis; + }) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); + } + return savedVis; + }) + .catch( + redirectWhenMissing({ + 'visualization': '/visualize', + 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id + }) + ); + } + } + }) + .when(`visualize/:tail*?`, { + redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts new file mode 100644 index 0000000000000..6adcfd2cc7186 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/** + * The imports in this file are static functions and types which still live in legacy folders and are used + * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually + * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported + * directly where they are needed. + */ + +import chrome from 'ui/chrome'; + +export const legacyChrome = chrome; + +// @ts-ignore +export { AppState, AppStateProvider } from 'ui/state_management/app_state'; +export { State } from 'ui/state_management/state'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { PersistedState } from 'ui/persisted_state'; + +export { npSetup, npStart } from 'ui/new_platform'; +export { IPrivate } from 'ui/private'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; + +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; + +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +export { configureAppAngularModule, ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; +// @ts-ignore +export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; + +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; + +// @ts-ignore +export { defaultEditor } from 'ui/vis/editors/default/default'; +export { VisType } from 'ui/vis'; +export { wrapInI18nContext } from 'ui/i18n'; + +export { VisSavedObject } from './embeddable/visualize_embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html index edb7cccbd46a2..4511ac61f7396 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -14,6 +14,8 @@ is-open="listingController.showNewVisModal" on-close="listingController.closeNewVisModal" vis-types-registry="listingController.visTypeRegistry" + add-base-path="listingController.addBasePath" + ui-settings="listingController.uiSettings" >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index f9e3a1a90115a..9b02be0581b8d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -24,36 +24,46 @@ import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; - -const { - addBasePath, - chrome, - chromeLegacy, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter, - toastNotifications, - uiModules, - wrapInI18nContext, - visualizations, -} = getServices(); - -const app = uiModules.get('app/visualize', ['ngRoute', 'react']); -app.directive('visualizeListingTable', reactDirective => - reactDirective(wrapInI18nContext(VisualizeListingTable)) -); -app.directive('newVisModal', reactDirective => reactDirective(wrapInI18nContext(NewVisModal))); +import { wrapInI18nContext } from '../legacy_imports'; + +export function initListingDirective(app) { + app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable))); + app.directive('newVisModal', reactDirective => + reactDirective(wrapInI18nContext(NewVisModal), [ + ['visTypesRegistry', { watchDepth: 'collection' }], + ['onClose', { watchDepth: 'reference' }], + ['addBasePath', { watchDepth: 'reference' }], + ['uiSettings', { watchDepth: 'reference' }], + 'isOpen', + ]) + ); +} export function VisualizeListingController($injector, createNewVis) { - const Private = $injector.get('Private'); - const config = $injector.get('config'); + const { + addBasePath, + chrome, + legacyChrome, + savedObjectRegistry, + savedObjectsClient, + data: { + query: { + timefilter: { timefilter }, + }, + }, + toastNotifications, + uiSettings, + visualizations, + core: { docLinks }, + } = getServices(); const kbnUrl = $injector.get('kbnUrl'); - const savedObjectClient = Private(SavedObjectsClientProvider); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); this.showNewVisModal = false; + this.addBasePath = addBasePath; + this.uiSettings = uiSettings; this.createNewVis = () => { this.showNewVisModal = true; @@ -82,14 +92,14 @@ export function VisualizeListingController($injector, createNewVis) { } // TODO: Extract this into an external service. - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; + const services = savedObjectRegistry.byLoaderPropertiesName; const visualizationService = services.visualizations; this.visTypeRegistry = visualizations.types; this.fetchItems = filter => { - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = uiSettings.get('visualize:enableLabs'); return visualizationService - .findListItems(filter, config.get('savedObjects:listingLimit')) + .findListItems(filter, uiSettings.get('savedObjects:listingLimit')) .then(result => { this.totalItems = result.total; @@ -103,11 +113,11 @@ export function VisualizeListingController($injector, createNewVis) { this.deleteSelectedItems = function deleteSelectedItems(selectedItems) { return Promise.all( selectedItems.map(item => { - return savedObjectClient.delete(item.savedObjectType, item.id); + return savedObjectsClient.delete(item.savedObjectType, item.id); }) ) .then(() => { - chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + legacyChrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { @@ -126,7 +136,7 @@ export function VisualizeListingController($injector, createNewVis) { }, ]); - this.listingLimit = config.get('savedObjects:listingLimit'); + this.listingLimit = uiSettings.get('savedObjects:listingLimit'); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index efab03303aa80..890fa64af9693 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -27,22 +27,21 @@ import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elas import { getServices } from '../kibana_services'; -const { capabilities, toastNotifications, uiSettings } = getServices(); - class VisualizeListingTable extends Component { constructor(props) { super(props); } render() { + const { visualizeCapabilities, uiSettings, toastNotifications } = getServices(); return ( item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts new file mode 100644 index 0000000000000..1aa2d70dabea6 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -0,0 +1,161 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; + +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; +import { VisualizeConstants } from './visualize_constants'; +import { setServices, VisualizeKibanaServices } from './kibana_services'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; +import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports'; +import { SavedVisualizations } from './types'; + +export interface LegacyAngularInjectedDependencies { + legacyChrome: any; + editorTypes: any; + savedObjectRegistry: any; + savedVisualizations: SavedVisualizations; +} + +export interface VisualizePluginStartDependencies { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + share: SharePluginStart; + visualizations: VisualizationsStart; +} + +export interface VisualizePluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + }; + home: HomePublicPluginSetup; + kibana_legacy: KibanaLegacySetup; +} + +export class VisualizePlugin implements Plugin { + private startDependencies: { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + savedObjectsClient: SavedObjectsClientContract; + share: SharePluginStart; + visualizations: VisualizationsStart; + } | null = null; + + public async setup( + core: CoreSetup, + { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies + ) { + kibana_legacy.registerLegacyApp({ + id: 'visualize', + title: 'Visualize', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + + const { + savedObjectsClient, + embeddables, + navigation, + visualizations, + data, + share, + } = this.startDependencies; + + const angularDependencies = await getAngularDependencies(); + const deps: VisualizeKibanaServices = { + ...angularDependencies, + addBasePath: contextCore.http.basePath.prepend, + core: contextCore as LegacyCoreStart, + chrome: contextCore.chrome, + data, + embeddables, + getBasePath: core.http.basePath.get, + indexPatterns: data.indexPatterns, + localStorage: new Storage(localStorage), + navigation, + savedObjectsClient, + savedQueryService: data.query.savedQueries, + share, + toastNotifications: contextCore.notifications.toasts, + uiSettings: contextCore.uiSettings, + visualizeCapabilities: contextCore.application.capabilities.visualize, + visualizations, + }; + setServices(deps); + + const { renderApp } = await import('./application'); + return renderApp(params.element, params.appBasePath, deps); + }, + }); + + home.featureCatalogue.register({ + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + + VisEditorTypesRegistryProvider.register(defaultEditor); + } + + public start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies + ) { + this.startDependencies = { + data, + embeddables, + navigation, + savedObjectsClient, + share, + visualizations, + }; + + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); + embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + } +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts index c83f7f5a5da8b..b6a3981215384 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from './kibana_services'; +import { VisSavedObject } from './legacy_imports'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts similarity index 67% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts rename to src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts index 5ebefd817ca4a..c64287a0e63b8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ + +import { IModule } from 'angular'; +import { VisualizeKibanaServices } from './kibana_services'; + +// @ts-ignore +import { initEditorDirective } from './editor/editor'; // @ts-ignore -import angular from 'angular'; -import { DashboardEmptyScreen } from './dashboard_empty_screen'; +import { initListingDirective } from './listing/visualize_listing'; -angular - .module('app/dashboard/emptyScreen', ['react']) - .directive('dashboardEmptyScreen', function(reactDirective: any) { - return reactDirective(DashboardEmptyScreen, [ - ['showLinkToVisualize', { watchDepth: 'value' }], - ['onLinkClick', { watchDepth: 'reference' }], - ]); - }); +export function initVisualizeAppDirective(app: IModule, deps: VisualizeKibanaServices) { + initEditorDirective(app, deps); + initListingDirective(app); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 4aa614b68ea23..5be5f58994887 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -2,6 +2,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`] = ` @@ -1287,6 +1307,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` exports[`NewVisModal should render as expected 1`] = ` diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 99d9590e750fd..0dd2091bbfee0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,33 +20,17 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisModal } from './new_vis_modal'; -import { VisType } from '../kibana_services'; +import { VisType } from '../legacy_imports'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -jest.mock('../kibana_services', () => { - const mock = { - addBasePath: jest.fn(path => `root${path}`), - uiSettings: { get: jest.fn() }, - createUiStatsReporter: () => jest.fn(), - }; - - return { - getServices: () => mock, - VisType: {}, - METRIC_TYPE: 'metricType', - }; -}); - -import { getServices } from '../kibana_services'; +jest.mock('../legacy_imports', () => ({ + State: () => null, + AppState: () => null, +})); -beforeEach(() => { - jest.clearAllMocks(); -}); +import { NewVisModal } from './new_vis_modal'; describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings.get as jest.Mock; - const defaultVisTypeParams = { hidden: false, visualization: class Controller { @@ -76,17 +60,36 @@ describe('NewVisModal', () => { }, getAliases: () => [], }; + const addBasePath = (url: string) => `testbasepath${url}`; + const settingsGet = jest.fn(); + const uiSettings: any = { get: settingsGet }; + + beforeEach(() => { + jest.clearAllMocks(); + }); it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper).toMatchSnapshot(); }); it('should show a button for regular visualizations', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); }); @@ -95,7 +98,13 @@ describe('NewVisModal', () => { it('should open the editor for visualizations without search', () => { window.location.assign = jest.fn(); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); visButton.simulate('click'); @@ -110,6 +119,8 @@ describe('NewVisModal', () => { onClose={() => null} visTypesRegistry={visTypes} editorParams={['foo=true', 'bar=42']} + addBasePath={addBasePath} + uiSettings={uiSettings} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,7 +132,13 @@ describe('NewVisModal', () => { describe('filter for visualization types', () => { it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); searchBox.simulate('change', { target: { value: 'with' } }); @@ -133,7 +150,13 @@ describe('NewVisModal', () => { it('should not show experimental visualizations if visualize:enableLabs is false', () => { settingsGet.mockReturnValue(false); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); }); @@ -141,7 +164,13 @@ describe('NewVisModal', () => { it('should show experimental visualizations if visualize:enableLabs is true', () => { settingsGet.mockReturnValue(true); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 420f0e5198056..0b46b562f2146 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,20 +22,21 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; +import { VisType } from '../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, METRIC_TYPE, VisType } from '../kibana_services'; - -const { addBasePath, createUiStatsReporter, uiSettings } = getServices(); - interface TypeSelectionProps { isOpen: boolean; onClose: () => void; visTypesRegistry: TypesStart; editorParams?: string[]; + addBasePath: (path: string) => string; + uiSettings: IUiSettingsClient; } interface TypeSelectionState { @@ -55,7 +56,7 @@ class NewVisModal extends React.Component ); @@ -124,7 +126,7 @@ class NewVisModal extends React.Component void; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index fa2ca6747bc40..92320f7bb443a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -20,7 +20,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { IUiSettingsClient } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -30,7 +31,9 @@ interface ShowNewVisModalParams { export function showNewVisModal( visTypeRegistry: TypesStart, - { editorParams = [] }: ShowNewVisModalParams = {} + { editorParams = [] }: ShowNewVisModalParams = {}, + addBasePath: (path: string) => string, + uiSettings: IUiSettingsClient ) { const container = document.createElement('div'); const onClose = () => { @@ -40,14 +43,16 @@ export function showNewVisModal( document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 382f475669f5d..3093499a030c8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -21,18 +21,6 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -jest.mock('../../kibana_services', () => { - return { - getServices: () => ({ - addBasePath: jest.fn((url: string) => `testbasepath${url}`), - }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe('NewVisHelp', () => { it('should render as expected', () => { expect( @@ -53,6 +41,7 @@ describe('NewVisHelp', () => { stage: 'production', }, ]} + addBasePath={(url: string) => `testbasepath${url}`} /> ) ).toMatchInlineSnapshot(` diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx index 44c36f7d17d55..107cbc0e754b5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -22,10 +22,9 @@ import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; -import { getServices } from '../../kibana_services'; - interface Props { promotedTypes: VisTypeAliasListEntry[]; + addBasePath: (path: string) => string; } export function NewVisHelp(props: Props) { @@ -43,7 +42,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

string; onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; visTypesRegistry: TypesStart; showExperimental: boolean; @@ -153,6 +154,7 @@ class TypeSelection extends React.Component t.promotion)} + addBasePath={this.props.addBasePath} /> )} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js index 4ffe790bd51a1..1f5ed443fee80 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js @@ -18,6 +18,7 @@ */ import { get, map } from 'lodash'; +import { abortableRequestHandler } from '../../../../../elasticsearch/lib/abortable_request_handler'; export function registerValueSuggestions(server) { const serverConfig = server.config(); @@ -26,7 +27,7 @@ export function registerValueSuggestions(server) { server.route({ path: '/api/kibana/suggestions/values/{index}', method: ['POST'], - handler: async function (req) { + handler: abortableRequestHandler(async function (signal, req) { const { index } = req.params; const { field: fieldName, query, boolFilter } = req.payload; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -46,7 +47,7 @@ export function registerValueSuggestions(server) { ); try { - const response = await callWithRequest(req, 'search', { index, body }); + const response = await callWithRequest(req, 'search', { index, body }, { signal }); const buckets = get(response, 'aggregations.suggestions.buckets') || get(response, 'aggregations.nestedSuggestions.suggestions.buckets') || []; @@ -55,7 +56,7 @@ export function registerValueSuggestions(server) { } catch (error) { throw server.plugins.elasticsearch.handleESError(error); } - }, + }), }); } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json index 69a165c09c2f9..9001613623ccb 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts new file mode 100644 index 0000000000000..65601b5371815 --- /dev/null +++ b/src/legacy/core_plugins/management/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; + +// eslint-disable-next-line import/no-default-export +export default function ManagementPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'management', + publicDir: resolve(__dirname, 'public'), + config: (Joi: any) => { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + init: (server: Legacy.Server) => ({}), + }; + + return new kibana.Plugin(config); +} diff --git a/src/legacy/core_plugins/management/package.json b/src/legacy/core_plugins/management/package.json new file mode 100644 index 0000000000000..77d33a7bce3b6 --- /dev/null +++ b/src/legacy/core_plugins/management/package.json @@ -0,0 +1,5 @@ +{ + "name": "management", + "version": "kibana" +} + \ No newline at end of file diff --git a/src/legacy/ui/public/management/saved_objects_management/index.ts b/src/legacy/core_plugins/management/public/index.ts similarity index 72% rename from src/legacy/ui/public/management/saved_objects_management/index.ts rename to src/legacy/core_plugins/management/public/index.ts index c7223a859ee37..3d64b6d2aa2bb 100644 --- a/src/legacy/ui/public/management/saved_objects_management/index.ts +++ b/src/legacy/core_plugins/management/public/index.ts @@ -17,13 +17,24 @@ * under the License. */ -export { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; +/** + * Static np-ready code, re-exported here so consumers can import from + * `src/legacy/core_plugins/management/public` + * + * @public + */ + export { + ManagementSetup, + ManagementStart, + plugin, + IndexPatternCreationConfig, + IndexPatternListConfig, SavedObjectsManagementAction, SavedObjectsManagementRecord, - SavedObjectsManagementRecordReference, -} from './saved_objects_management_action'; +} from './np_ready'; + export { processImportResponse, ProcessedImportResponse, -} from '../../../../core_plugins/kibana/public/management/sections/objects/lib/process_import_response'; +} from '../../kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts new file mode 100644 index 0000000000000..7c17f0c6bddc0 --- /dev/null +++ b/src/legacy/core_plugins/management/public/legacy.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +/** + * New Platform Shim + * + * In this file, we import any legacy dependencies we have, and shim them into + * our plugin by manually constructing the values that the new platform will + * eventually be passing to the `setup/start` method of our plugin definition. + * + * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy + * world code. Then when it comes time to migrate to the new platform, we can + * simply delete this shim file. + * + * We are also calling `setup/start` here and exporting our public contract so that + * other legacy plugins are able to import from '../core_plugins/visualizations/legacy' + * and receive the response value of the `setup/start` contract, mimicking the + * data that will eventually be injected by the new platform. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from '.'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, {}); +export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/management/public/np_ready/index.ts b/src/legacy/core_plugins/management/public/np_ready/index.ts new file mode 100644 index 0000000000000..ec93516df8349 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/index.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/** + * Management Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ +import { PluginInitializerContext } from 'src/core/public'; +import { ManagementPlugin } from './plugin'; +export { ManagementSetup, ManagementStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ManagementPlugin(initializerContext); +} + +export { + IndexPatternCreationConfig, + IndexPatternListConfig, +} from './services/index_pattern_management'; + +export { + SavedObjectsManagementAction, + SavedObjectsManagementRecord, +} from './services/saved_objects_management'; diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/legacy/core_plugins/management/public/np_ready/mocks.ts new file mode 100644 index 0000000000000..13a0cf4c891a3 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/mocks.ts @@ -0,0 +1,66 @@ +/* + * 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 { PluginInitializerContext } from 'src/core/public'; +import { coreMock } from '../../../../../core/public/mocks'; +import { ManagementSetup, ManagementStart, ManagementPlugin } from './plugin'; + +const createSetupContract = (): ManagementSetup => ({ + indexPattern: { + creation: { + add: jest.fn(), + getType: jest.fn(), + getIndexPatternCreationOptions: jest.fn(), + } as any, + list: { + add: jest.fn(), + getIndexPatternTags: jest.fn(), + getFieldInfo: jest.fn(), + areScriptedFieldsEnabled: jest.fn(), + } as any, + }, + savedObjects: { + registry: { + register: jest.fn(), + has: jest.fn(), + get: jest.fn(() => []), + }, + }, +}); + +const createStartContract = (): ManagementStart => ({}); + +const createInstance = async () => { + const plugin = new ManagementPlugin({} as PluginInitializerContext); + + const setup = plugin.setup(coreMock.createSetup(), {}); + const doStart = () => plugin.start(coreMock.createStart(), {}); + + return { + plugin, + setup, + doStart, + }; +}; + +export const mockManagementPlugin = { + createSetupContract, + createStartContract, + createInstance, +}; diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/legacy/core_plugins/management/public/np_ready/plugin.ts new file mode 100644 index 0000000000000..032a46439ba55 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/plugin.ts @@ -0,0 +1,67 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; +import { + SavedObjectsManagementService, + SavedObjectsManagementServiceSetup, +} from './services/saved_objects_management'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginSetupDependencies {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginStartDependencies {} + +export interface ManagementSetup { + indexPattern: IndexPatternManagementSetup; + savedObjects: SavedObjectsManagementServiceSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ManagementStart {} + +export class ManagementPlugin + implements + Plugin< + ManagementSetup, + ManagementStart, + ManagementPluginSetupDependencies, + ManagementPluginStartDependencies + > { + private readonly indexPattern = new IndexPatternManagementService(); + private readonly savedObjects = new SavedObjectsManagementService(); + + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, deps: ManagementPluginSetupDependencies) { + return { + indexPattern: this.indexPattern.setup({ httpClient: core.http }), + savedObjects: this.savedObjects.setup(), + }; + } + + public start(core: CoreStart, plugins: ManagementPluginStartDependencies) { + return {}; + } + + public stop() { + this.indexPattern.stop(); + } +} diff --git a/src/legacy/ui/public/state_management/state_hashing/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index.ts similarity index 85% rename from src/legacy/ui/public/state_management/state_hashing/index.ts rename to src/legacy/core_plugins/management/public/np_ready/services/index.ts index 6225202f90978..4d55fce3d8a7d 100644 --- a/src/legacy/ui/public/state_management/state_hashing/index.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { hashUrl, unhashUrl, hashQuery, unhashQuery } from './hash_unhash_url'; -export { createStateHash, isStateHash } from './state_hash'; +export * from './index_pattern_management'; +export * from './saved_objects_management'; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts similarity index 55% rename from src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts index 9cf1e6c4ed373..0598c88c80ba7 100644 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts @@ -19,17 +19,39 @@ import { i18n } from '@kbn/i18n'; -const indexPatternTypeName = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'index pattern' }); - -const indexPatternButtonText = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Standard index pattern' }); - -const indexPatternButtonDescription = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform full aggregations against any data' }); +const indexPatternTypeName = i18n.translate( + 'management.editIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'index pattern' } +); + +const indexPatternButtonText = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Standard index pattern' } +); + +const indexPatternButtonDescription = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform full aggregations against any data' } +); + +export type UrlHandler = (url: string) => void; + +export interface IndexPatternCreationOption { + text: string; + description: string; + testSubj: string; + onClick: () => void; + isBeta?: boolean; +} export class IndexPatternCreationConfig { - static key = 'default'; + public readonly key = 'default'; + + protected type?: string; + protected name: string; + protected showSystemIndices: boolean; + protected httpClient: object | null; + protected isBeta: boolean; constructor({ type = undefined, @@ -37,6 +59,12 @@ export class IndexPatternCreationConfig { showSystemIndices = true, httpClient = null, isBeta = false, + }: { + type?: string; + name?: string; + showSystemIndices?: boolean; + httpClient?: object | null; + isBeta?: boolean; }) { this.type = type; this.name = name; @@ -45,7 +73,9 @@ export class IndexPatternCreationConfig { this.isBeta = isBeta; } - async getIndexPatternCreationOption(urlHandler) { + public async getIndexPatternCreationOption( + urlHandler: UrlHandler + ): Promise { return { text: indexPatternButtonText, description: indexPatternButtonDescription, @@ -56,39 +86,39 @@ export class IndexPatternCreationConfig { }; } - getIndexPatternType = () => { + public getIndexPatternType() { return this.type; } - getIndexPatternName = () => { + public getIndexPatternName() { return this.name; } - getIsBeta = () => { + public getIsBeta() { return this.isBeta; } - getShowSystemIndices = () => { + public getShowSystemIndices() { return this.showSystemIndices; } - getIndexTags() { + public getIndexTags() { return []; } - checkIndicesForErrors = () => { + public checkIndicesForErrors() { return undefined; } - getIndexPatternMappings = () => { + public getIndexPatternMappings() { return {}; } - renderPrompt = () => { + public renderPrompt() { return null; } - getFetchForWildcardOptions = () => { + public getFetchForWildcardOptions() { return {}; } } diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts new file mode 100644 index 0000000000000..84c4c28aa2260 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { IndexPatternCreationConfig } from './config'; +export { IndexPatternCreationManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts new file mode 100644 index 0000000000000..605ffdd6f2134 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts @@ -0,0 +1,61 @@ +/* + * 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 { HttpServiceBase } from '../../../../../../../../core/public'; +import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; + +export class IndexPatternCreationManager { + private configs: IndexPatternCreationConfig[]; + + constructor(private readonly httpClient: HttpServiceBase) { + this.configs = []; + } + + public add(Config: typeof IndexPatternCreationConfig) { + const config = new Config({ httpClient: this.httpClient }); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternCreationManager.`); + } + this.configs.push(config); + } + + public getType(key: string | undefined): IndexPatternCreationConfig | null { + if (key) { + const index = this.configs.findIndex(config => config.key === key); + return this.configs[index] || null; + } else { + return this.getType('default'); + } + } + + public async getIndexPatternCreationOptions(urlHandler: UrlHandler) { + const options: IndexPatternCreationOption[] = []; + await Promise.all( + this.configs.map(async config => { + const option = config.getIndexPatternCreationOption + ? await config.getIndexPatternCreationOption(urlHandler) + : null; + if (option) { + options.push(option); + } + }) + ); + return options; + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts new file mode 100644 index 0000000000000..2abe13eb0e292 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './index_pattern_management_service'; +export { IndexPatternCreationConfig } from './creation'; +export { IndexPatternListConfig } from './list'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts new file mode 100644 index 0000000000000..b9e07564a324c --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts @@ -0,0 +1,53 @@ +/* + * 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 { HttpServiceBase } from '../../../../../../../core/public'; +import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; +import { IndexPatternListManager, IndexPatternListConfig } from './list'; + +interface SetupDependencies { + httpClient: HttpServiceBase; +} + +/** + * Index patterns management service + * + * @internal + */ +export class IndexPatternManagementService { + public setup({ httpClient }: SetupDependencies) { + const creation = new IndexPatternCreationManager(httpClient); + const list = new IndexPatternListManager(); + + creation.add(IndexPatternCreationConfig); + list.add(IndexPatternListConfig); + + return { + creation, + list, + }; + } + + public stop() { + // nothing to do here yet. + } +} + +/** @internal */ +export type IndexPatternManagementSetup = ReturnType; diff --git a/src/plugins/kibana_utils/public/store/types.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts similarity index 50% rename from src/plugins/kibana_utils/public/store/types.ts rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts index 952ee07f18baf..dd4d77a681171 100644 --- a/src/plugins/kibana_utils/public/store/types.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts @@ -17,26 +17,35 @@ * under the License. */ -import { Observable } from 'rxjs'; -import { Store as ReduxStore } from 'redux'; +import { i18n } from '@kbn/i18n'; +import { IIndexPattern, IFieldType } from 'src/plugins/data/public'; -export interface AppStore< - State extends {}, - StateMutators extends Mutators> = {} -> { - redux: ReduxStore; - get: () => State; - set: (state: State) => void; - state$: Observable; - createMutators: >(pureMutators: M) => Mutators; - mutators: StateMutators; +export interface IndexPatternTag { + key: string; + name: string; } -export type PureMutator = (state: State) => (...args: any[]) => State; -export type Mutator> = (...args: Parameters>) => void; +export class IndexPatternListConfig { + public readonly key = 'default'; -export interface PureMutators { - [name: string]: PureMutator; -} + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean): IndexPatternTag[] { + return isDefault + ? [ + { + key: 'default', + name: i18n.translate('management.editIndexPattern.list.defaultIndexPatternListName', { + defaultMessage: 'Default', + }), + }, + ] + : []; + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return []; + } -export type Mutators> = { [K in keyof M]: Mutator }; + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return true; + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts new file mode 100644 index 0000000000000..114226b3a4570 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { IndexPatternListConfig } from './config'; +export { IndexPatternListManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts new file mode 100644 index 0000000000000..73ca33ae914a9 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IIndexPattern, IFieldType } from 'src/plugins/data/public'; +import { IndexPatternListConfig, IndexPatternTag } from './config'; + +export class IndexPatternListManager { + private configs: IndexPatternListConfig[]; + + constructor() { + this.configs = []; + } + + public add(Config: typeof IndexPatternListConfig) { + const config = new Config(); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternListManager.`); + } + this.configs.push(config); + } + + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { + return this.configs.reduce((tags: IndexPatternTag[], config) => { + return config.getIndexPatternTags + ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) + : tags; + }, []); + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return this.configs.reduce((info: string[], config) => { + return config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info; + }, []); + } + + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return this.configs.every(config => { + return config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true; + }); + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts new file mode 100644 index 0000000000000..bad3b3ac36ef7 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './saved_objects_management_action_registry'; +export * from './saved_objects_management_action'; +export * from './saved_objects_management_service'; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts similarity index 96% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts index a09f842e36713..d83afb195a492 100644 --- a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ReactNode } from '@elastic/eui/node_modules/@types/react'; +import { ReactNode } from 'react'; export interface SavedObjectsManagementRecordReference { type: string; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts similarity index 67% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts index 8445863b6dfc3..d5e90d12cccc9 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts @@ -16,10 +16,17 @@ * specific language governing permissions and limitations * under the License. */ +import { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; -import { DevToolsSettings } from '../../../../../services'; +export class SavedObjectsManagementService { + public setup() { + return { + registry: SavedObjectsManagementActionRegistry, + }; + } -export function applyCurrentSettings(editor: any, settings: DevToolsSettings) { - editor.getSession().setUseWrapMode(settings.wrapMode); - editor.$el.css('font-size', settings.fontSize + 'px'); + public stop() {} } + +/** @internal */ +export type SavedObjectsManagementServiceSetup = ReturnType; diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js index 1aa7bce2af699..7fbf715ac3616 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js @@ -18,7 +18,7 @@ */ import chrome from 'ui/chrome'; -import { hashUrl } from 'ui/state_management/state_hashing'; +import { hashUrl } from '../../../../plugins/kibana_utils/public'; import uiRoutes from 'ui/routes'; import { fatalError } from 'ui/notify'; 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_timeseries/public/_mixins.scss b/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss index aefebe8117bbf..8d3aea2a3ae54 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/_mixins.scss @@ -1,6 +1,3 @@ -@import 'node_modules/@elastic/eui/src/components/form/variables'; -@import 'node_modules/@elastic/eui/src/components/form/mixins'; - @mixin tvbEditorRepeatingRow { background-color: $euiColorLightestShade; padding: $euiSizeS; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss b/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss index 3372367be273d..99e024c9c5193 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/_annotations_editor.scss @@ -1,11 +1,9 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - .tvbAnnotationsEditor__container { padding: $euiSize; background-color: $euiColorLightestShade; } -@include euiPanel('tvbAnnotationsEditor'); +@include euiPanel('.tvbAnnotationsEditor'); .tvbAnnotationsEditor { margin-bottom: $euiSize; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss b/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss index 0cfe5fef010d3..82d1f4ea62676 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/_series_editor.scss @@ -1,10 +1,8 @@ -@import 'node_modules/@elastic/eui/src/components/panel/mixins'; - .tvbSeriesEditor__container { padding: $euiSize; } -@include euiPanel('tvbSeriesEditor'); +@include euiPanel('.tvbSeriesEditor'); .tvbSeriesEditor { margin-bottom: $euiSize; 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/server/config/schema.js b/src/legacy/server/config/schema.js index 24fe4e1a4d986..a19a39da0f6dd 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -135,7 +135,14 @@ export default () => Joi.object({ then: Joi.default(!process.stdout.isTTY), otherwise: Joi.default(true) }), - timezone: Joi.string() + timezone: Joi.string(), + rotate: Joi.object().keys({ + enabled: Joi.boolean().default(false), + everyBytes: Joi.number().greater(1024).default(10485760), + keepFiles: Joi.number().greater(2).less(1024).default(7), + pollingInterval: Joi.number().greater(5000).less(3600000).default(10000), + usePolling: Joi.boolean().default(false) + }).default() }).default(), ops: Joi.object({ diff --git a/src/legacy/server/logging/index.js b/src/legacy/server/logging/index.js index 6e07757abf7bc..defbcd3331df4 100644 --- a/src/legacy/server/logging/index.js +++ b/src/legacy/server/logging/index.js @@ -20,6 +20,7 @@ import good from '@elastic/good'; import loggingConfiguration from './configuration'; import { logWithMetadata } from './log_with_metadata'; +import { setupLoggingRotate } from './rotate'; export async function setupLogging(server, config) { return await server.register({ @@ -30,5 +31,7 @@ export async function setupLogging(server, config) { export async function loggingMixin(kbnServer, server, config) { logWithMetadata.decorateServer(server); - return await setupLogging(server, config); + + await setupLogging(server, config); + await setupLoggingRotate(server, config); } diff --git a/src/legacy/server/logging/rotate/index.ts b/src/legacy/server/logging/rotate/index.ts new file mode 100644 index 0000000000000..646c89efe8e20 --- /dev/null +++ b/src/legacy/server/logging/rotate/index.ts @@ -0,0 +1,59 @@ +/* + * 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 { isMaster, isWorker } from 'cluster'; +import { Server } from 'hapi'; +import { LogRotator } from './log_rotator'; +import { KibanaConfig } from '../../kbn_server'; + +let logRotator: LogRotator; + +export async function setupLoggingRotate(server: Server, config: KibanaConfig) { + // If log rotate is not enabled we skip + if (!config.get('logging.rotate.enabled')) { + return; + } + + // We just want to start the logging rotate service once + // and we choose to use the master (prod) or the worker server (dev) + if (!isMaster && isWorker && process.env.kbnWorkerType !== 'server') { + return; + } + + // We don't want to run logging rotate server if + // we are not logging to a file + if (config.get('logging.dest') === 'stdout') { + server.log( + ['warning', 'logging:rotate'], + 'Log rotation is enabled but logging.dest is configured for stdout. Set logging.dest to a file for this setting to take effect.' + ); + return; + } + + // Enable Logging Rotate Service + // We need the master process and it can + // try to setupLoggingRotate more than once, + // so we'll need to assure it only loads once. + if (!logRotator) { + logRotator = new LogRotator(config, server); + await logRotator.start(); + } + + return logRotator; +} diff --git a/src/legacy/server/logging/rotate/log_rotator.test.ts b/src/legacy/server/logging/rotate/log_rotator.test.ts new file mode 100644 index 0000000000000..c2100546364d4 --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.test.ts @@ -0,0 +1,265 @@ +/* + * 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 del from 'del'; +import fs, { existsSync, mkdirSync, statSync, writeFileSync } from 'fs'; +import { LogRotator } from './log_rotator'; +import { tmpdir } from 'os'; +import { dirname, join } from 'path'; + +const mockOn = jest.fn(); +jest.mock('chokidar', () => ({ + watch: jest.fn(() => ({ + on: mockOn, + close: jest.fn(), + })), +})); + +jest.mock('lodash', () => ({ + ...require.requireActual('lodash'), + throttle: (fn: any) => fn, +})); + +const tempDir = join(tmpdir(), 'kbn_log_rotator_test'); +const testFilePath = join(tempDir, 'log_rotator_test_log_file.log'); + +const createLogRotatorConfig: any = (logFilePath: string) => { + return new Map([ + ['logging.dest', logFilePath], + ['logging.rotate.everyBytes', 2], + ['logging.rotate.keepFiles', 2], + ['logging.rotate.usePolling', false], + ['logging.rotate.pollingInterval', 10000], + ] as any); +}; + +const mockServer: any = { + log: jest.fn(), +}; + +const writeBytesToFile = (filePath: string, numberOfBytes: number) => { + writeFileSync(filePath, 'a'.repeat(numberOfBytes), { flag: 'a' }); +}; + +describe('LogRotator', () => { + beforeEach(() => { + mkdirSync(tempDir, { recursive: true }); + writeFileSync(testFilePath, ''); + }); + + afterEach(() => { + del.sync(dirname(testFilePath), { force: true }); + mockOn.mockClear(); + }); + + it('rotates log file when bigger than set limit on start', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + await logRotator.stop(); + const testLogFileDir = dirname(testFilePath); + + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when equal than set limit over time', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 1); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when file size is bigger than limit', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 3 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file service correctly keeps number of files', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly keeps number of files even when number setting changes', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + + logRotator.keepFiles = 1; + await logRotator.start(); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly detects usePolling when it should be false', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(false); + + const usePolling = await logRotator._shouldUsePolling(); + expect(usePolling).toBe(false); + + await logRotator.stop(); + }); + + it('rotates log file service correctly detects usePolling when it should be true', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((eventType, cb) => { + if (eventType === 'error') { + cb(); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + }); + + it('rotates log file service correctly fallback to usePolling true after defined timeout', async () => { + jest.useFakeTimers(); + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((ev: string) => { + if (ev === 'error') { + jest.runTimersToTime(15000); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + jest.useRealTimers(); + }); +}); diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts new file mode 100644 index 0000000000000..3662910ca5a7b --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.ts @@ -0,0 +1,359 @@ +/* + * 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 chokidar from 'chokidar'; +import { isMaster } from 'cluster'; +import fs from 'fs'; +import { Server } from 'hapi'; +import { throttle } from 'lodash'; +import { tmpdir } from 'os'; +import { basename, dirname, join, sep } from 'path'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { promisify } from 'util'; +import { KibanaConfig } from '../../kbn_server'; + +const mkdirAsync = promisify(fs.mkdir); +const readdirAsync = promisify(fs.readdir); +const renameAsync = promisify(fs.rename); +const statAsync = promisify(fs.stat); +const unlinkAsync = promisify(fs.unlink); +const writeFileAsync = promisify(fs.writeFile); + +export class LogRotator { + private readonly config: KibanaConfig; + private readonly log: Server['log']; + public logFilePath: string; + public everyBytes: number; + public keepFiles: number; + public running: boolean; + private logFileSize: number; + public isRotating: boolean; + public throttledRotate: () => void; + public stalker: chokidar.FSWatcher | null; + public usePolling: boolean; + public pollingInterval: number; + private stalkerUsePollingPolicyTestTimeout: NodeJS.Timeout | null; + + constructor(config: KibanaConfig, server: Server) { + this.config = config; + this.log = server.log.bind(server); + this.logFilePath = config.get('logging.dest'); + this.everyBytes = config.get('logging.rotate.everyBytes'); + this.keepFiles = config.get('logging.rotate.keepFiles'); + this.running = false; + this.logFileSize = 0; + this.isRotating = false; + this.throttledRotate = throttle(async () => await this._rotate(), 5000); + this.stalker = null; + this.usePolling = config.get('logging.rotate.usePolling'); + this.pollingInterval = config.get('logging.rotate.pollingInterval'); + this.stalkerUsePollingPolicyTestTimeout = null; + } + + async start() { + if (this.running) { + return; + } + + this.running = true; + + // create exit listener for cleanup purposes + this._createExitListener(); + + // call rotate on startup + await this._callRotateOnStartup(); + + // init log file size monitor + await this._startLogFileSizeMonitor(); + } + + stop = () => { + if (!this.running) { + return; + } + + // cleanup exit listener + this._deleteExitListener(); + + // stop log file size monitor + this._stopLogFileSizeMonitor(); + + this.running = false; + }; + + async _shouldUsePolling() { + try { + // Setup a test file in order to try the fs env + // and understand if we need to usePolling or not + const tempFileDir = tmpdir(); + const tempFile = join(tempFileDir, 'kbn_log_rotation_use_polling_test_file.log'); + + await mkdirAsync(tempFileDir, { recursive: true }); + await writeFileAsync(tempFile, ''); + + // setup fs.watch for the temp test file + const testWatcher = fs.watch(tempFile, { persistent: false }); + + // await writeFileAsync(tempFile, 'test'); + + const usePollingTest$ = new Observable(observer => { + // observable complete function + const completeFn = (completeStatus: boolean) => { + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + testWatcher.close(); + + observer.next(completeStatus); + observer.complete(); + }; + + // setup conditions that would fire the observable + this.stalkerUsePollingPolicyTestTimeout = setTimeout(() => completeFn(true), 15000); + testWatcher.on('change', () => completeFn(false)); + testWatcher.on('error', () => completeFn(true)); + + // fire test watcher events + setTimeout(() => { + fs.writeFileSync(tempFile, 'test'); + }, 0); + }); + + // wait for the first observable result and consider it as the result + // for our use polling test + const usePollingTestResult = await usePollingTest$.pipe(first()).toPromise(); + + // delete the temp file used for the test + await unlinkAsync(tempFile); + + return usePollingTestResult; + } catch { + return true; + } + } + + async _startLogFileSizeMonitor() { + this.usePolling = await this._shouldUsePolling(); + + if (this.usePolling && this.usePolling !== this.config.get('logging.rotate.usePolling')) { + this.log( + ['warning', 'logging:rotate'], + 'The current environment does not support `fs.watch`. Falling back to polling using `fs.watchFile`' + ); + } + + this.stalker = chokidar.watch(this.logFilePath, { + ignoreInitial: true, + awaitWriteFinish: false, + useFsEvents: false, + usePolling: this.usePolling, + interval: this.pollingInterval, + binaryInterval: this.pollingInterval, + alwaysStat: true, + atomic: false, + }); + this.stalker.on('change', this._logFileSizeMonitorHandler); + } + + _logFileSizeMonitorHandler = async (filename: string, stats: fs.Stats) => { + if (!filename || !stats) { + return; + } + + this.logFileSize = stats.size || 0; + await this.throttledRotate(); + }; + + _stopLogFileSizeMonitor() { + if (!this.stalker) { + return; + } + + this.stalker.close(); + + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + } + + _createExitListener() { + process.on('exit', this.stop); + } + + _deleteExitListener() { + process.removeListener('exit', this.stop); + } + + async _getLogFileSizeAndCreateIfNeeded() { + try { + const logFileStats = await statAsync(this.logFilePath); + return logFileStats.size; + } catch { + // touch the file to make the watcher being able to register + // change events + await writeFileAsync(this.logFilePath, ''); + return 0; + } + } + + async _callRotateOnStartup() { + this.logFileSize = await this._getLogFileSizeAndCreateIfNeeded(); + await this._rotate(); + } + + _shouldRotate() { + // should rotate evaluation + // 1. should rotate if current log size exceeds + // the defined one on everyBytes + // 2. should not rotate if is already rotating or if any + // of the conditions on 1. do not apply + if (this.isRotating) { + return false; + } + + return this.logFileSize >= this.everyBytes; + } + + async _rotate() { + if (!this._shouldRotate()) { + return; + } + + await this._rotateNow(); + } + + async _rotateNow() { + // rotate process + // 1. get rotated files metadata (list of log rotated files present on the log folder, numerical sorted) + // 2. delete last file + // 3. rename all files to the correct index +1 + // 4. rename + compress current log into 1 + // 5. send SIGHUP to reload log config + + // rotate process is starting + this.isRotating = true; + + // get rotated files metadata + const foundRotatedFiles = await this._readRotatedFilesMetadata(); + + // delete number of rotated files exceeding the keepFiles limit setting + const rotatedFiles: string[] = await this._deleteFoundRotatedFilesAboveKeepFilesLimit( + foundRotatedFiles + ); + + // delete last file + await this._deleteLastRotatedFile(rotatedFiles); + + // rename all files to correct index + 1 + // and normalize numbering if by some reason + // (for example log file deletion) that numbering + // was interrupted + await this._renameRotatedFilesByOne(rotatedFiles); + + // rename current log into 0 + await this._rotateCurrentLogFile(); + + // send SIGHUP to reload log configuration + this._sendReloadLogConfigSignal(); + + // Reset log file size + this.logFileSize = 0; + + // rotate process is finished + this.isRotating = false; + } + + async _readRotatedFilesMetadata() { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + const foundLogFiles: string[] = await readdirAsync(logFilesFolder); + + return ( + foundLogFiles + .filter(file => new RegExp(`${logFileBaseName}\\.\\d`).test(file)) + // we use .slice(-1) here in order to retrieve the last number match in the read filenames + .sort((a, b) => Number(a.match(/(\d+)/g)!.slice(-1)) - Number(b.match(/(\d+)/g)!.slice(-1))) + .map(filename => `${logFilesFolder}${sep}${filename}`) + ); + } + + async _deleteFoundRotatedFilesAboveKeepFilesLimit(foundRotatedFiles: string[]) { + if (foundRotatedFiles.length <= this.keepFiles) { + return foundRotatedFiles; + } + + const finalRotatedFiles = foundRotatedFiles.slice(0, this.keepFiles); + const rotatedFilesToDelete = foundRotatedFiles.slice( + finalRotatedFiles.length, + foundRotatedFiles.length + ); + + await Promise.all( + rotatedFilesToDelete.map((rotatedFilePath: string) => unlinkAsync(rotatedFilePath)) + ); + + return finalRotatedFiles; + } + + async _deleteLastRotatedFile(rotatedFiles: string[]) { + if (rotatedFiles.length < this.keepFiles) { + return; + } + + const lastFilePath: string = rotatedFiles.pop() as string; + await unlinkAsync(lastFilePath); + } + + async _renameRotatedFilesByOne(rotatedFiles: string[]) { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + + for (let i = rotatedFiles.length - 1; i >= 0; i--) { + const oldFilePath = rotatedFiles[i]; + const newFilePath = `${logFilesFolder}${sep}${logFileBaseName}.${i + 1}`; + await renameAsync(oldFilePath, newFilePath); + } + } + + async _rotateCurrentLogFile() { + const newFilePath = `${this.logFilePath}.0`; + await renameAsync(this.logFilePath, newFilePath); + } + + _sendReloadLogConfigSignal() { + if (isMaster) { + (process as NodeJS.EventEmitter).emit('SIGHUP'); + return; + } + + // Send a special message to the cluster manager + // so it can forward it correctly + // It will only run when we are under cluster mode (not under a production environment) + if (!process.send) { + this.log( + ['error', 'logging:rotate'], + 'For some unknown reason process.send is not defined, the rotation was not successful' + ); + return; + } + + process.send(['RELOAD_LOGGING_CONFIG_FROM_SERVER_WORKER']); + } +} diff --git a/src/legacy/server/sass/build.test.js b/src/legacy/server/sass/build.test.js index 810b7cb44b917..bde552e9df00f 100644 --- a/src/legacy/server/sass/build.test.js +++ b/src/legacy/server/sass/build.test.js @@ -47,14 +47,35 @@ it('builds light themed SASS', async () => { expect(readFileSync(targetPath, 'utf8').replace(/(\/\*# sourceMappingURL=).*( \*\/)/, '$1...$2')) .toMatchInlineSnapshot(` -"foo bar { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: #e6f0f8 url(./images/img.png) url(ui/assets/favicons/favicon.ico); } -/*# sourceMappingURL=... */" -`); + "/* 1 */ + /* 1 */ + /** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a + + + ); +}; +``` 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 22ac720246d4b..3f5aeebac54d8 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -19,12 +19,13 @@ 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'; +export * from './state_management/url'; 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/state_containers/index.ts b/src/plugins/kibana_utils/public/state_containers/index.ts new file mode 100644 index 0000000000000..43e204ecb79f7 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_containers/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './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/legacy/core_plugins/console/np_ready/public/application/models/index.ts b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts similarity index 96% rename from src/legacy/core_plugins/console/np_ready/public/application/models/index.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/index.ts index c4ac6f1454b7f..0e52c4c55872d 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/index.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './legacy_editor'; +export * from './state_hash'; diff --git a/src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts similarity index 91% rename from src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts index 83a94e37785c4..cccb74acaf1e5 100644 --- a/src/legacy/ui/public/state_management/state_hashing/state_hash.test.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.test.ts @@ -18,9 +18,8 @@ */ import { encode as encodeRison } from 'rison-node'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { mockStorage } from '../../../../../plugins/kibana_utils/public/storage/hashed_item_store/mock'; -import { createStateHash, isStateHash } from '../state_hashing'; +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { createStateHash, isStateHash } from './state_hash'; describe('stateHash', () => { beforeEach(() => { diff --git a/src/legacy/ui/public/state_management/state_hashing/state_hash.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts similarity index 96% rename from src/legacy/ui/public/state_management/state_hashing/state_hash.ts rename to src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts index b3574876bafae..a3eb5272b112d 100644 --- a/src/legacy/ui/public/state_management/state_hashing/state_hash.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts @@ -18,7 +18,7 @@ */ import { Sha256 } from '../../../../../core/public/utils'; -import { hashedItemStore } from '../../../../../plugins/kibana_utils/public'; +import { hashedItemStore } from '../../storage/hashed_item_store'; // This prefix is used to identify hash strings that have been encoded in the URL. const HASH_PREFIX = 'h@'; diff --git a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts similarity index 97% rename from src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts rename to src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts index afbe86a4b4d12..a85158acddefd 100644 --- a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts @@ -17,9 +17,8 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { mockStorage } from '../../../../../plugins/kibana_utils/public/storage/hashed_item_store/mock'; -import { HashedItemStore } from '../../../../../plugins/kibana_utils/public'; +import { mockStorage } from '../../storage/hashed_item_store/mock'; +import { HashedItemStore } from '../../storage/hashed_item_store'; import { hashUrl, unhashUrl } from './hash_unhash_url'; describe('hash unhash url', () => { diff --git a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts similarity index 94% rename from src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts rename to src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts index 7142683c25115..872e7953f938b 100644 --- a/src/legacy/ui/public/state_management/state_hashing/hash_unhash_url.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts @@ -22,8 +22,8 @@ import rison, { RisonObject } from 'rison-node'; import { stringify as stringifyQueryString } from 'querystring'; import encodeUriQuery from 'encode-uri-query'; import { format as formatUrl, parse as parseUrl } from 'url'; -import { hashedItemStore } from '../../../../../plugins/kibana_utils/public'; -import { createStateHash, isStateHash } from './state_hash'; +import { hashedItemStore } from '../../storage/hashed_item_store'; +import { createStateHash, isStateHash } from '../state_hash'; export type IParsedUrlQuery = Record; @@ -98,7 +98,7 @@ export function retrieveState(stateHash: string): RisonObject { const json = hashedItemStore.getItem(stateHash); const throwUnableToRestoreUrlError = () => { throw new Error( - i18n.translate('common.ui.stateManagement.unableToRestoreUrlErrorMessage', { + i18n.translate('kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage', { defaultMessage: 'Unable to completely restore the URL, be sure to use the share functionality.', }) @@ -125,7 +125,7 @@ export function persistState(state: RisonObject): string { if (isItemSet) return hash; // If we ran out of space trying to persist the state, notify the user. const message = i18n.translate( - 'common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage', + 'kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage', { defaultMessage: 'Kibana is unable to store history items in your session ' + diff --git a/src/plugins/kibana_utils/public/store/index.ts b/src/plugins/kibana_utils/public/state_management/url/index.ts similarity index 93% rename from src/plugins/kibana_utils/public/store/index.ts rename to src/plugins/kibana_utils/public/state_management/url/index.ts index 468e8ab8c5ade..30c5696233db7 100644 --- a/src/plugins/kibana_utils/public/store/index.ts +++ b/src/plugins/kibana_utils/public/state_management/url/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './create_store'; -export * from './react'; +export * from './hash_unhash_url'; 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/tasks/config/run.js b/tasks/config/run.js index e4071c8b7d0ab..97a0f381f2aa4 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -269,7 +269,6 @@ module.exports = function (grunt) { '--config', 'test/plugin_functional/config.js', '--bail', '--debug', - '--kibana-install-dir', KIBANA_INSTALL_DIR, ], }), diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index bc70339bd1a66..d72722af06bff 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -72,7 +72,7 @@ export default function ({ getService }) { readFromDocValues: true, }, { - aggregatable: true, + aggregatable: false, esTypes: [ 'keyword' ], @@ -156,7 +156,7 @@ export default function ({ getService }) { readFromDocValues: true, }, { - aggregatable: true, + aggregatable: false, esTypes: [ 'keyword' ], diff --git a/test/common/services/kibana_server/kibana_server.ts b/test/common/services/kibana_server/kibana_server.ts index f7b7885840455..16039d6fee833 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/test/common/services/kibana_server/kibana_server.ts @@ -32,7 +32,7 @@ export function KibanaServerProvider({ getService }: FtrProviderContext) { const kbn = new KbnClient(log, [url], defaults); if (defaults) { - lifecycle.on('beforeTests', async () => { + lifecycle.beforeTests.add(async () => { await kbn.uiSettings.update(defaults); }); } diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index e18fd47b39b16..bf549ec21a6d3 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -78,7 +78,6 @@ export default function ({ getService, getPageObjects }) { const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton(); await logoButton.moveMouseTo(); await PageObjects.dashboard.clickExitFullScreenTextButton(); - await retry.try(async () => { const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 94b2941ecd3d1..58eef9fbf6b87 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -25,7 +25,6 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - const filterBar = getService('filterBar'); const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { @@ -173,20 +172,6 @@ export default function ({ getService, getPageObjects }) { }); - describe('filter editor', function () { - it('should add a phrases filter', async function () { - await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); - expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); - }); - - it('should show the phrases if you re-open a phrases filter', async function () { - await filterBar.clickEditFilter('extension.raw', 'jpg'); - const phrases = await filterBar.getFilterEditorSelectedPhrases(); - expect(phrases.length).to.be(1); - expect(phrases[0]).to.be('jpg'); - }); - }); - describe('data-shared-item', function () { it('should have correct data-shared-item title and description', async () => { const expected = { diff --git a/test/functional/apps/discover/_filter_editor.js b/test/functional/apps/discover/_filter_editor.js new file mode 100644 index 0000000000000..cab16252add4a --- /dev/null +++ b/test/functional/apps/discover/_filter_editor.js @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const filterBar = getService('filterBar'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + const defaultSettings = { + defaultIndex: 'logstash-*', + }; + + describe('discover filter editor', function describeIndexTests() { + + before(async function () { + log.debug('load kibana index with default index pattern'); + await esArchiver.loadIfNeeded('discover'); + + // and load a set of makelogs data + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + log.debug('discover filter editor'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + describe('filter editor', function () { + it('should add a phrases filter', async function () { + await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); + expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); + }); + + it('should show the phrases if you re-open a phrases filter', async function () { + await filterBar.clickEditFilter('extension.raw', 'jpg'); + const phrases = await filterBar.getFilterEditorSelectedPhrases(); + expect(phrases.length).to.be(1); + expect(phrases[0]).to.be('jpg'); + await filterBar.ensureFieldEditorModalIsClosed(); + }); + + it('should support filtering on nested fields', async () => { + await filterBar.addFilter('nestedField.child', 'is', 'nestedValue'); + expect(await filterBar.hasFilter('nestedField.child', 'nestedValue')).to.be(true); + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be( + '1' + ); + }); + }); + }); + }); +} 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/apps/discover/index.js b/test/functional/apps/discover/index.js index 902490bebd1ac..28df897b67c09 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -34,6 +34,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_saved_queries')); loadTestFile(require.resolve('./_discover')); + loadTestFile(require.resolve('./_filter_editor')); loadTestFile(require.resolve('./_errors')); loadTestFile(require.resolve('./_field_data')); loadTestFile(require.resolve('./_shared_links')); 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/failure_debugging.ts b/test/functional/services/failure_debugging.ts index e2be69d779799..cd12f1b75c828 100644 --- a/test/functional/services/failure_debugging.ts +++ b/test/functional/services/failure_debugging.ts @@ -65,5 +65,6 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); } - lifecycle.on('testFailure', onFailure).on('testHookFailure', onFailure); + lifecycle.testFailure.add(onFailure); + lifecycle.testHookFailure.add(onFailure); } diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index adf0f2266ba17..9d494b1e6d950 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -166,6 +166,7 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon if (cancelSaveFilterModalButtonExists) { await testSubjects.click('cancelSaveFilter'); } + await testSubjects.waitForDeleted('cancelSaveFilter'); } /** diff --git a/test/functional/services/remote/poll_for_log_entry.ts b/test/functional/services/remote/poll_for_log_entry.ts index 71e2711906fce..0bd5d94f892b0 100644 --- a/test/functional/services/remote/poll_for_log_entry.ts +++ b/test/functional/services/remote/poll_for_log_entry.ts @@ -29,7 +29,7 @@ export function pollForLogEntry$( driver: WebDriver, type: string, ms: number, - stop$: Rx.Observable + stop$: Rx.Observable ) { const logCtrl = driver.manage().logs(); const poll$ = new Rx.BehaviorSubject(undefined); diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 380c33e93ad90..90ff55fbebde5 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -80,7 +80,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { driver, logging.Type.BROWSER, config.get('browser.logPollingMs'), - lifecycle.cleanup$ as any + lifecycle.cleanup.after$ ) .pipe( mergeMap(logEntry => { @@ -110,7 +110,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } } - lifecycle.on('beforeTests', async () => { + lifecycle.beforeTests.add(async () => { // hard coded default, can be overridden per suite using `browser.setWindowSize()` // and will be automatically reverted after each suite await driver @@ -120,7 +120,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }); const windowSizeStack: Array<{ width: number; height: number }> = []; - lifecycle.on('beforeTestSuite', async () => { + lifecycle.beforeTestSuite.add(async () => { windowSizeStack.unshift( await driver .manage() @@ -129,11 +129,11 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { ); }); - lifecycle.on('beforeEachTest', async () => { + lifecycle.beforeEachTest.add(async () => { await driver.manage().setTimeouts({ implicit: config.get('timeouts.find') }); }); - lifecycle.on('afterTestSuite', async () => { + lifecycle.afterTestSuite.add(async () => { const { width, height } = windowSizeStack.shift()!; await driver .manage() @@ -143,7 +143,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { await clearBrowserStorage('localStorage'); }); - lifecycle.on('cleanup', async () => { + lifecycle.cleanup.add(async () => { if (logSubscription) { await new Promise(r => logSubscription!.add(r)); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 50303fbad7aab..1a7abc6317075 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -115,9 +115,9 @@ async function attemptToCreateCommand( session, logging.Type.BROWSER, logPollingMs, - lifecycle.cleanup$ + lifecycle.cleanup.after$ ).pipe( - takeUntil(lifecycle.cleanup$), + takeUntil(lifecycle.cleanup.after$), map(({ message, level: { name: level } }) => ({ message: message.replace(/\\n/g, '\n'), level, @@ -151,7 +151,7 @@ async function attemptToCreateCommand( } const { input, chunk$, cleanup } = await createStdoutSocket(); - lifecycle.on('cleanup', cleanup); + lifecycle.cleanup.add(cleanup); const session = await new Builder() .forBrowser(browserType) 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/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 69f958fb91f60..ae4fa1578fb2b 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "react": "^16.12.0", "react-dom": "^16.12.0" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 96a5e26a74109..32da6eade2b42 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "react": "^16.12.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index e95aa04ddd6b1..0ab65667d6f82 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index f874cecd5d812..6f661ab2a79e1 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "react": "^16.12.0" }, "scripts": { diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js b/test/typings/encode_uri_query.d.ts similarity index 79% rename from src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js rename to test/typings/encode_uri_query.d.ts index 9b1bfca284ee6..4bfc554624446 100644 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js +++ b/test/typings/encode_uri_query.d.ts @@ -17,10 +17,8 @@ * under the License. */ -import { uiRegistry } from 'ui/registry/_registry'; - -export const IndexPatternListConfigRegistry = uiRegistry({ - name: 'indexPatternList', - index: ['name'], - order: ['order'], -}); +declare module 'encode-uri-query' { + function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; + // eslint-disable-next-line import/no-default-export + export default encodeUriQuery; +} diff --git a/test/typings/rison_node.d.ts b/test/typings/rison_node.d.ts new file mode 100644 index 0000000000000..2592c36e8ae9a --- /dev/null +++ b/test/typings/rison_node.d.ts @@ -0,0 +1,39 @@ +/* + * 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. + */ + +declare module 'rison-node' { + export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface RisonArray extends Array {} + + export interface RisonObject { + [key: string]: RisonValue; + } + + export const decode: (input: string) => RisonValue; + + // eslint-disable-next-line @typescript-eslint/camelcase + export const decode_object: (input: string) => RisonObject; + + export const encode: (input: Input) => string; + + // eslint-disable-next-line @typescript-eslint/camelcase + export const encode_object: (input: Input) => string; +} diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index fd31a4d8b6e4f..4ad97f8d98717 100644 --- a/test/visual_regression/services/visual_testing/visual_testing.ts +++ b/test/visual_regression/services/visual_testing/visual_testing.ts @@ -54,7 +54,7 @@ export async function VisualTestingProvider({ getService }: FtrProviderContext) const lifecycle = getService('lifecycle'); let currentTest: Test | undefined; - lifecycle.on('beforeEachTest', (test: Test) => { + lifecycle.beforeEachTest.add(test => { currentTest = test; }); 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/actions/server/action_type_registry.test.ts b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts index b4d73cc4759d7..c0a01bc85e916 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts @@ -18,7 +18,7 @@ const actionTypeRegistryParams = { beforeEach(() => jest.resetAllMocks()); const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; describe('register()', () => { diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/legacy/plugins/actions/server/actions_client.test.ts index 1c10d1b1a83af..1cbf3949d20f8 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.test.ts @@ -30,7 +30,7 @@ const actionTypeRegistryParams = { let actionsClient: ActionsClient; let actionTypeRegistry: ActionTypeRegistry; const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; beforeEach(() => { diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts index a378d8a4b9b55..dd2bd328ce53f 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts @@ -174,19 +174,17 @@ async function executor( result = await sendEmail(logger, sendEmailOptions); } catch (err) { const message = i18n.translate('xpack.actions.builtin.email.errorSendingErrorMessage', { - defaultMessage: 'error in action "{actionId}" sending email: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error sending email', }); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } - return { status: 'ok', data: result }; + return { status: 'ok', data: result, actionId }; } // utilities diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts index 01853ee9cb870..0e9fe0483ee1e 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts @@ -60,13 +60,11 @@ async function executor( if (config.index == null && params.index == null) { const message = i18n.translate('xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage', { - defaultMessage: 'index param needs to be set because not set in config for action {actionId}', - values: { - actionId, - }, + defaultMessage: 'index param needs to be set because not set in config for action', }); return { status: 'error', + actionId, message, }; } @@ -101,17 +99,15 @@ async function executor( result = await services.callCluster('bulk', bulkParams); } catch (err) { const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { - defaultMessage: 'error in action "{actionId}" indexing data: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error indexing documents', }); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } - return { status: 'ok', data: result }; + return { status: 'ok', data: result, actionId }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index 1d453d2bd2340..def3b7eea21d9 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -148,11 +148,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for trigger', async () => { @@ -212,11 +213,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for acknowledge', async () => { @@ -267,11 +269,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for resolve', async () => { @@ -322,11 +325,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should fail when sendPagerdury throws', async () => { @@ -348,11 +352,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: doing some testing", - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event", + "serviceMessage": "doing some testing", + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 429', async () => { @@ -374,12 +380,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: status 429, retry later", - "retry": true, - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: http status 429, retry later", + "retry": true, + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 501', async () => { @@ -401,12 +408,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: status 501, retry later", - "retry": true, - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: http status 501, retry later", + "retry": true, + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 418', async () => { @@ -428,10 +436,11 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: unexpected status 418", - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: unexpected status 418", + "status": "error", + } + `); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts index ec45e298d3902..e1437b8eef554 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -123,16 +123,14 @@ async function executor( response = await postPagerduty({ apiUrl, data, headers, services }); } catch (err) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', { - defaultMessage: 'error in pagerduty action "{actionId}" posting event: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error posting pagerduty event', }); logger.warn(`error thrown posting pagerduty event: ${err.message}`); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } @@ -141,38 +139,37 @@ async function executor( if (response.status === 202) { return { status: 'ok', + actionId, data: response.data, }; } if (response.status === 429 || response.status >= 500) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingRetryErrorMessage', { - defaultMessage: - 'error in pagerduty action "{actionId}" posting event: status {status}, retry later', + defaultMessage: 'error posting pagerduty event: http status {status}, retry later', values: { - actionId, status: response.status, }, }); return { status: 'error', + actionId, message, retry: true, }; } const message = i18n.translate('xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage', { - defaultMessage: - 'error in pagerduty action "{actionId}" posting event: unexpected status {status}', + defaultMessage: 'error posting pagerduty event: unexpected status {status}', values: { - actionId, status: response.status, }, }); return { status: 'error', + actionId, message, }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts index f17430b734c66..c2af29051d8dd 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts @@ -52,17 +52,15 @@ async function executor( logger[params.level](params.message); } catch (err) { const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { - defaultMessage: 'error in action "{actionId}" logging message: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error logging message', }); return { status: 'error', message, + serviceMessage: err.message, + actionId, }; } - return { status: 'ok' }; + return { status: 'ok', actionId }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts index 8ec569a69ff23..29b89150e3990 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts @@ -69,7 +69,7 @@ async function slackExecutor( result = await webhook.send(message); } catch (err) { if (err.original == null || err.original.response == null) { - return errorResult(actionId, err.message); + return serviceErrorResult(actionId, err.message); } const { status, statusText, headers } = err.original.response; @@ -88,7 +88,17 @@ async function slackExecutor( ); } - return errorResult(actionId, `${err.message} - ${statusText}`); + const errMessage = i18n.translate( + 'xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage', + { + defaultMessage: 'unexpected http response from slack: {httpStatus} {httpStatusText}', + values: { + httpStatus: status, + httpStatusText: statusText, + }, + } + ); + return errorResult(actionId, errMessage); } if (result == null) { @@ -102,55 +112,52 @@ async function slackExecutor( } if (result.text !== 'ok') { - const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage', - { - defaultMessage: 'unexpected text response from slack', - } - ); - return errorResult(actionId, errMessage); + return serviceErrorResult(actionId, result.text); } - return successResult(result); + return successResult(actionId, result); } -function successResult(data: any): ActionTypeExecutorResult { - return { status: 'ok', data }; +function successResult(actionId: string, data: any): ActionTypeExecutorResult { + return { status: 'ok', data, actionId }; } -function errorResult(id: string, message: string): ActionTypeExecutorResult { +function errorResult(actionId: string, message: string): ActionTypeExecutorResult { + return { + status: 'error', + message, + actionId, + }; +} +function serviceErrorResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.slack.errorPostingErrorMessage', { - defaultMessage: 'an error occurred in action "{id}" posting a slack message: {message}', - values: { - id, - message, - }, + defaultMessage: 'error posting slack message', }); return { status: 'error', message: errMessage, + actionId, + serviceMessage, }; } -function retryResult(id: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, message: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage', { - defaultMessage: 'an error occurred in action "{id}" posting a slack message, retry later', - values: { - id, - }, + defaultMessage: 'error posting a slack message, retry later', } ); return { status: 'error', message: errMessage, retry: true, + actionId, }; } function retryResultSeconds( - id: string, + actionId: string, message: string, retryAfter: number ): ActionTypeExecutorResult { @@ -160,12 +167,9 @@ function retryResultSeconds( const errMessage = i18n.translate( 'xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage', { - defaultMessage: - 'an error occurred in action "{id}" posting a slack message, retry at {retryString}: {message}', + defaultMessage: 'error posting a slack message, retry at {retryString}', values: { - id, retryString, - message, }, } ); @@ -173,5 +177,7 @@ function retryResultSeconds( status: 'error', message: errMessage, retry, + actionId, + serviceMessage: message, }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts index b91fd86b0b682..06fe2fb0e591c 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts @@ -113,7 +113,7 @@ export async function executor( } = result; logger.debug(`response from webhook action "${actionId}": [HTTP ${status}] ${statusText}`); - return successResult(data); + return successResult(actionId, data); } else { const { error } = result; @@ -139,70 +139,58 @@ export async function executor( return errorResultInvalid(actionId, message); } - const message = i18n.translate('xpack.actions.builtin.webhook.unreachableRemoteWebhook', { - defaultMessage: 'Unreachable Remote Webhook, are you sure the address is correct?', - }); - logger.warn(`error on ${actionId} webhook action: ${message}`); - return errorResultUnreachable(actionId, message); + logger.warn(`error on ${actionId} webhook action: unexpected error`); + return errorResultUnexpectedError(actionId); } } // Action Executor Result w/ internationalisation -function successResult(data: any): ActionTypeExecutorResult { - return { status: 'ok', data }; +function successResult(actionId: string, data: any): ActionTypeExecutorResult { + return { status: 'ok', data, actionId }; } -function errorResultInvalid(id: string, message: string): ActionTypeExecutorResult { +function errorResultInvalid(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.invalidResponseErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook: {message}', - values: { - id, - message, - }, + defaultMessage: 'error calling webhook, invalid response', }); return { status: 'error', message: errMessage, + actionId, + serviceMessage, }; } -function errorResultUnreachable(id: string, message: string): ActionTypeExecutorResult { +function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.unreachableErrorMessage', { - defaultMessage: - 'Unreachable Webhook: an error occurred in webhook action "{id}" calling a remote webhook: {message}', - values: { - id, - message, - }, + defaultMessage: 'error calling webhook, unexpected error', }); return { status: 'error', message: errMessage, + actionId, }; } -function retryResult(id: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook, retry later', - values: { - id, - }, + defaultMessage: 'error calling webhook, retry later', } ); return { status: 'error', message: errMessage, retry: true, + actionId, + serviceMessage, }; } function retryResultSeconds( - id: string, - message: string, + actionId: string, + serviceMessage: string, retryAfter: number ): ActionTypeExecutorResult { @@ -212,12 +200,9 @@ function retryResultSeconds( const errMessage = i18n.translate( 'xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook, retry at {retryString}: {message}', + defaultMessage: 'error calling webhook, retry at {retryString}', values: { - id, retryString, - message, }, } ); @@ -225,5 +210,7 @@ function retryResultSeconds( status: 'error', message: errMessage, retry, + actionId, + serviceMessage, }; } diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 5ed67ae82b0ce..6767468509d25 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -157,6 +157,7 @@ test('throws an error when config is invalid', async () => { const result = await actionExecutor.execute(executeParams); expect(result).toEqual({ + actionId: '1', status: 'error', retry: false, message: `error validating action type config: [param1]: expected value of type [string] but got [undefined]`, @@ -188,6 +189,7 @@ test('throws an error when params is invalid', async () => { const result = await actionExecutor.execute(executeParams); expect(result).toEqual({ + actionId: '1', status: 'error', retry: false, message: `error validating action params: [param1]: expected value of type [string] but got [undefined]`, diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts index 1afb8a8870215..c532b76a904d5 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts @@ -91,7 +91,7 @@ export class ActionExecutor { validatedConfig = validateConfig(actionType, config); validatedSecrets = validateSecrets(actionType, secrets); } catch (err) { - return { status: 'error', message: err.message, retry: false }; + return { status: 'error', actionId, message: err.message, retry: false }; } let result: ActionTypeExecutorResult | null = null; @@ -113,7 +113,7 @@ export class ActionExecutor { logger.debug(`action executed successfully: ${actionLabel}`); // return basic response if none provided - if (result == null) return { status: 'ok' }; + if (result == null) return { status: 'ok', actionId }; return result; } diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index a5bf42bc2cc01..41a7c17a02c5a 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -94,7 +94,7 @@ test('executes the task by calling the executor with proper parameters', async ( taskInstance: mockedTaskInstance, }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', @@ -154,6 +154,7 @@ test('throws an error with suggested retry logic when return status is error', a }); mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'error', + actionId: '2', message: 'Error message', data: { foo: true }, retry: false, @@ -174,7 +175,7 @@ test('uses API key when provided', async () => { taskInstance: mockedTaskInstance, }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', @@ -217,7 +218,7 @@ test(`doesn't use API key when not provided`, async () => { factory.initialize(taskRunnerFactoryInitializerParams); const taskRunner = factory.create({ taskInstance: mockedTaskInstance }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', diff --git a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts index 4cb28728fb421..28122c72baf65 100644 --- a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts @@ -10,7 +10,7 @@ import { validateParams, validateConfig, validateSecrets } from './validate_with import { ActionType, ExecutorType } from '../types'; const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; test('should validate when there are no validators', () => { diff --git a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts b/x-pack/legacy/plugins/actions/server/routes/execute.test.ts index cd5b9c7c4a7e8..b0ba5a8a0f566 100644 --- a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts +++ b/x-pack/legacy/plugins/actions/server/routes/execute.test.ts @@ -31,11 +31,11 @@ it('executes an action with proper parameters', async () => { callCluster: jest.fn(), savedObjectsClient: jest.fn(), }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '1' }); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); - expect(payload).toBe('{"status":"ok"}'); + expect(JSON.parse(payload)).toEqual({ status: 'ok', actionId: '1' }); expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ actionId: '1', diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/legacy/plugins/actions/server/types.ts index 5a74241bc4829..94b34034cd8b2 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/legacy/plugins/actions/server/types.ts @@ -51,8 +51,10 @@ export interface FindActionResult extends ActionResult { // the result returned from an action type executor function export interface ActionTypeExecutorResult { + actionId: string; status: 'ok' | 'error'; message?: string; + serviceMessage?: string; data?: any; retry?: null | boolean | Date; } diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index f840c019d5e02..7a7446602351d 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -25,6 +25,9 @@ "actionRef": { "type": "keyword" }, + "actionTypeId": { + "type": "keyword" + }, "params": { "enabled": false, "type": "object" diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 08607f04a5235..8ff54e25a0c99 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -74,6 +74,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -87,6 +99,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -133,6 +146,7 @@ describe('create()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -157,6 +171,7 @@ describe('create()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, @@ -224,6 +239,184 @@ describe('create()', () => { `); }); + test('creates an alert with multiple actions', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + const data = getMockData({ + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + { + id: '2', + type: 'action', + attributes: { + actionTypeId: 'test2', + }, + references: [], + }, + ], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + interval: '10s', + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_1', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_2', + actionTypeId: 'test2', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'action_1', + type: 'action', + id: '1', + }, + { + name: 'action_2', + type: 'action', + id: '2', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: 'idle', + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + scheduledTaskId: 'task-123', + }, + references: [], + }); + const result = await alertsClient.create({ data }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test2", + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "id": "1", + "interval": "10s", + "params": Object { + "bar": true, + }, + "scheduledTaskId": "task-123", + } + `); + expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ + { + id: '1', + type: 'action', + }, + { + id: '2', + type: 'action', + }, + ]); + }); + test('creates a disabled alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData({ enabled: false }); @@ -233,6 +426,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -247,6 +452,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -266,6 +472,7 @@ describe('create()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -306,6 +513,23 @@ describe('create()', () => { ); }); + test('throws error if loading actions fails', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + const data = getMockData(); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.bulkGet.mockRejectedValueOnce(new Error('Test Error')); + await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Test Error"` + ); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + test('throws error if create saved object fails', async () => { const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); @@ -315,6 +539,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure')); await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( `"Test failure"` @@ -331,6 +567,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -344,6 +592,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -381,6 +630,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -394,6 +655,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -442,6 +704,18 @@ describe('create()', () => { created: true, result: { id: '123', api_key: 'abc' }, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -455,6 +729,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -506,6 +781,7 @@ describe('create()', () => { { actionRef: 'action_0', group: 'default', + actionTypeId: 'test', params: { foo: true }, }, ], @@ -1149,6 +1425,18 @@ describe('update()', () => { references: [], version: '123', }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.update.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -1162,6 +1450,7 @@ describe('update()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -1201,6 +1490,7 @@ describe('update()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -1226,6 +1516,7 @@ describe('update()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, @@ -1262,6 +1553,183 @@ describe('update()', () => { `); }); + it('updates with multiple actions', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + scheduledTaskId: 'task-123', + }, + references: [], + version: '123', + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + { + id: '2', + type: 'action', + attributes: { + actionTypeId: 'test2', + }, + references: [], + }, + ], + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + interval: '10s', + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_1', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_2', + actionTypeId: 'test2', + params: { + foo: true, + }, + }, + ], + scheduledTaskId: 'task-123', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'action_1', + type: 'action', + id: '1', + }, + { + name: 'action_2', + type: 'action', + id: '2', + }, + ], + }); + const result = await alertsClient.update({ + id: '1', + data: { + interval: '10s', + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }, + }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test2", + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "enabled": true, + "id": "1", + "interval": "10s", + "params": Object { + "bar": true, + }, + "scheduledTaskId": "task-123", + } + `); + expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ + { + id: '1', + type: 'action', + }, + { + id: '2', + type: 'action', + }, + ]); + }); + it('calls the createApiKey function', async () => { const alertsClient = new AlertsClient(alertsClientParams); alertTypeRegistry.get.mockReturnValueOnce({ @@ -1281,6 +1749,18 @@ describe('update()', () => { references: [], version: '123', }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ created: true, result: { id: '123', api_key: 'abc' }, @@ -1298,6 +1778,7 @@ describe('update()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -1338,6 +1819,7 @@ describe('update()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -1364,6 +1846,7 @@ describe('update()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 3916ec1d62b6c..27fda9871e685 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -21,6 +21,7 @@ interface SuccessCreateAPIKeyResult { result: SecurityPluginCreateAPIKeyResult; } export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult; +type NormalizedAlertAction = Omit; interface ConstructorOptions { logger: Logger; @@ -62,9 +63,15 @@ interface CreateOptions { Alert, Exclude< keyof Alert, - 'createdBy' | 'updatedBy' | 'apiKey' | 'apiKeyOwner' | 'muteAll' | 'mutedInstanceIds' + | 'createdBy' + | 'updatedBy' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' > - >; + > & { actions: NormalizedAlertAction[] }; options?: { migrationVersion?: Record; }; @@ -76,7 +83,7 @@ interface UpdateOptions { name: string; tags: string[]; interval: string; - actions: AlertAction[]; + actions: NormalizedAlertAction[]; params: Record; }; } @@ -117,8 +124,10 @@ export class AlertsClient { this.validateActions(alertType, data.actions); - const { alert: rawAlert, references } = this.getRawAlert({ + const { references, actions } = await this.denormalizeActions(data.actions); + const rawAlert: RawAlert = { ...data, + actions, createdBy: username, updatedBy: username, apiKeyOwner: apiKey.created && username ? username : undefined, @@ -128,7 +137,7 @@ export class AlertsClient { params: validatedAlertTypeParams, muteAll: false, mutedInstanceIds: [], - }); + }; const createdAlert = await this.savedObjectsClient.create('alert', rawAlert, { ...options, references, @@ -202,7 +211,7 @@ export class AlertsClient { const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); this.validateActions(alertType, data.actions); - const { actions, references } = this.extractReferences(data.actions); + const { actions, references } = await this.denormalizeActions(data.actions); const username = await this.getUserName(); const updatedObject = await this.savedObjectsClient.update( 'alert', @@ -371,26 +380,6 @@ export class AlertsClient { }); } - private extractReferences(actions: Alert['actions']) { - const references: SavedObjectReference[] = []; - const rawActions = actions.map((action, i) => { - const actionRef = `action_${i}`; - references.push({ - name: actionRef, - type: 'action', - id: action.id, - }); - return { - ...omit(action, 'id'), - actionRef, - }; - }) as RawAlert['actions']; - return { - actions: rawActions, - references, - }; - } - private injectReferencesIntoActions( actions: RawAlert['actions'], references: SavedObjectReference[] @@ -426,19 +415,7 @@ export class AlertsClient { }; } - private getRawAlert(alert: Alert): { alert: RawAlert; references: SavedObjectReference[] } { - const { references, actions } = this.extractReferences(alert.actions); - return { - alert: { - ...alert, - actions, - }, - references, - }; - } - - private validateActions(alertType: AlertType, actions: Alert['actions']) { - // TODO: Should also ensure user has access to each action + private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void { const { actionGroups: alertTypeActionGroups } = alertType; const usedAlertActionGroups = actions.map(action => action.group); const invalidActionGroups = usedAlertActionGroups.filter( @@ -455,4 +432,41 @@ export class AlertsClient { ); } } + + private async denormalizeActions( + alertActions: NormalizedAlertAction[] + ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { + // Fetch action objects in bulk + const actionIds = [...new Set(alertActions.map(alertAction => alertAction.id))]; + const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); + const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); + const actionMap = new Map(); + for (const action of bulkGetResult.saved_objects) { + if (action.error) { + throw Boom.badRequest( + `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` + ); + } + actionMap.set(action.id, action); + } + // Extract references and set actionTypeId + const references: SavedObjectReference[] = []; + const actions = alertActions.map(({ id, ...alertAction }, i) => { + const actionRef = `action_${i}`; + references.push({ + id, + name: actionRef, + type: 'action', + }); + return { + ...alertAction, + actionRef, + actionTypeId: actionMap.get(id).attributes.actionTypeId, + }; + }); + return { + actions, + references, + }; + } } diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts index 4f523f203f87a..d86a06767c9d1 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts @@ -28,6 +28,7 @@ const createExecutionHandlerParams = { { id: '1', group: 'default', + actionTypeId: 'test', params: { foo: true, contextVal: 'My {{context.value}} goes here', diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 318dbdf068d6a..634a797880812 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -41,6 +41,12 @@ test('creates an alert with proper parameters', async () => { alertsClient.create.mockResolvedValueOnce({ ...mockedAlert, id: '123', + actions: [ + { + ...mockedAlert.actions[0], + actionTypeId: 'test', + }, + ], }); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); @@ -49,6 +55,7 @@ test('creates an alert with proper parameters', async () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "2", "params": Object { @@ -97,33 +104,4 @@ test('creates an alert with proper parameters', async () => { }, ] `); - expect(alertsClient.create).toHaveBeenCalledTimes(1); - expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "data": Object { - "actions": Array [ - Object { - "group": "default", - "id": "2", - "params": Object { - "foo": true, - }, - }, - ], - "alertTypeId": "1", - "enabled": true, - "interval": "10s", - "name": "abc", - "params": Object { - "bar": true, - }, - "tags": Array [ - "foo", - ], - "throttle": null, - }, - }, - ] - `); }); diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.ts b/x-pack/legacy/plugins/alerting/server/routes/create.ts index fb82a03f172b3..cb5277ae19100 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.ts @@ -6,7 +6,6 @@ import Hapi from 'hapi'; import Joi from 'joi'; -import { AlertAction } from '../types'; import { getDurationSchema } from '../lib'; interface ScheduleRequest extends Hapi.Request { @@ -16,7 +15,11 @@ interface ScheduleRequest extends Hapi.Request { tags: string[]; alertTypeId: string; interval: string; - actions: AlertAction[]; + actions: Array<{ + group: string; + id: string; + params: Record; + }>; params: Record; throttle: string | null; }; diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index 19618bc9e39fe..4d44ee9dfe6bd 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -21,6 +21,7 @@ const mockedAlert = { { group: 'default', id: '2', + actionTypeId: 'test', params: { foo: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts index 7fc3f45911010..334fb2120319d 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts @@ -24,6 +24,7 @@ const mockedResponse = { { group: 'default', id: '2', + actionTypeId: 'test', params: { baz: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.ts b/x-pack/legacy/plugins/alerting/server/routes/update.ts index 6aeedb93a1098..6e8f8557fb24a 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.ts @@ -6,7 +6,6 @@ import Joi from 'joi'; import Hapi from 'hapi'; -import { AlertAction } from '../types'; import { getDurationSchema } from '../lib'; interface UpdateRequest extends Hapi.Request { @@ -18,7 +17,11 @@ interface UpdateRequest extends Hapi.Request { name: string; tags: string[]; interval: string; - actions: AlertAction[]; + actions: Array<{ + group: string; + id: string; + params: Record; + }>; params: Record; throttle: string | null; }; diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index e2460c549c05d..1bec2632d8082 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -49,12 +49,14 @@ export type AlertActionParams = SavedObjectAttributes; export interface AlertAction { group: string; id: string; + actionTypeId: string; params: AlertActionParams; } export interface RawAlertAction extends SavedObjectAttributes { group: string; actionRef: string; + actionTypeId: string; params: AlertActionParams; } 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/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot index ef301c08cdfe8..6c9d4ce4459b3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot @@ -12,6 +12,7 @@ exports[`Storyshots arguments/AxisConfig extended 1`] = ` >
-

Cycle Slides -

+

-

Cycle Slides -

+

-

Cycle Slides -

+

-

Hide Toolbar -

+
-

Hide Toolbar -

+
-

Hide Toolbar -

+
can navigate Autoplay Settings 2`] = ` -

Cycle Slides -

+

can navigate Autoplay Settings 2`] = ` >
can navigate Autoplay Settings 2`] = ` >
can navigate Toolbar Settings, closes when activated 2`] = >
can navigate Toolbar Settings, closes when activated 2`] = -

Hide Toolbar -

+
can navigate Toolbar Settings, closes when activated 2`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings

Hide Toolbar

Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 8093c57d2631a..8c6ddfebcb6cc 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -18,7 +18,6 @@ import 'uiExports/contextMenuActions'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/inspectorViews'; import 'uiExports/interpreter'; import 'uiExports/savedObjectTypes'; 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/common/inventory_models/layouts.ts b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts index 9fce720f5b14b..0c593bec1af3a 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts @@ -11,7 +11,6 @@ * crashing due to the requirement of the `window` object. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { ReactNode, FunctionComponent } from 'react'; @@ -32,7 +31,7 @@ const layouts: Layouts = { }; export const findLayout = (type: InventoryItemType) => { - const Layout = idx(layouts, _ => _[type]); + const Layout = layouts?.[type]; if (!Layout) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findLayout.error', { diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts index 661b9c7a8841e..dc3c409ac497e 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx/target'; import { ReactNode, FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { InventoryItemType } from './types'; @@ -24,7 +23,7 @@ const toolbars: Toolbars = { }; export const findToolbar = (type: InventoryItemType) => { - const Toolbar = idx(toolbars, _ => _[type]); + const Toolbar = toolbars?.[type]; if (!Toolbar) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findToolbar.error', { 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/apps/start_app.tsx b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx index cd28114327b6d..41479cf6351ec 100644 --- a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx @@ -21,6 +21,7 @@ import { InfraFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; import { createStore } from '../store'; import { ApolloClientContext } from '../utils/apollo_context'; +import { ReduxStateContextProvider } from '../utils/redux_context'; import { HistoryContext } from '../utils/history_context'; import { useUiSetting$, @@ -46,15 +47,17 @@ export async function startApp(libs: InfraFrontendLibs) { - - - - - - - - - + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx index 549ca4c1ae047..4cefbea7225ec 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx @@ -18,7 +18,7 @@ interface LogTextStreamLoadingItemViewProps { hasMore: boolean; isLoading: boolean; isStreaming: boolean; - lastStreamingUpdate: number | null; + lastStreamingUpdate: Date | null; onLoadMore?: () => void; } diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 674c3f59ce957..a5b85788fdea9 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -39,7 +39,7 @@ interface ScrollableLogTextStreamViewProps { hasMoreBeforeStart: boolean; hasMoreAfterEnd: boolean; isStreaming: boolean; - lastLoadedTime: number | null; + lastLoadedTime: Date | null; target: TimeKey | null; jumpToTarget: (target: TimeKey) => any; reportVisibleInterval: (params: { @@ -143,7 +143,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< const hasItems = items.length > 0; return ( - {isReloading && !hasItems ? ( + {isReloading && (!isStreaming || !hasItems) ? ( extends React.PureComponent< // Flag the scrollTop change that's about to happen as programmatic, as // opposed to being in direct response to user input this.nextScrollEventFromCenterTarget = true; - scrollRef.current.scrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; - return true; + const currentScrollTop = scrollRef.current.scrollTop; + const newScrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; + scrollRef.current.scrollTop = newScrollTop; + return currentScrollTop !== newScrollTop; } return false; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts new file mode 100644 index 0000000000000..83bae37c348d4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts @@ -0,0 +1,64 @@ +/* + * 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 { ApolloClient } from 'apollo-client'; +import { TimeKey } from '../../../../common/time'; +import { logEntriesQuery } from '../../../graphql/log_entries.gql_query'; +import { useApolloClient } from '../../../utils/apollo_context'; +import { LogEntriesResponse } from '.'; + +const LOAD_CHUNK_SIZE = 200; + +type LogEntriesGetter = ( + client: ApolloClient<{}>, + countBefore: number, + countAfter: number +) => (params: { + sourceId: string; + timeKey: TimeKey | null; + filterQuery: string | null; +}) => Promise; + +const getLogEntries: LogEntriesGetter = (client, countBefore, countAfter) => async ({ + sourceId, + timeKey, + filterQuery, +}) => { + if (!timeKey) throw new Error('TimeKey is null'); + const result = await client.query({ + query: logEntriesQuery, + variables: { + sourceId, + timeKey: { time: timeKey.time, tiebreaker: timeKey.tiebreaker }, + countBefore, + countAfter, + filterQuery, + }, + fetchPolicy: 'no-cache', + }); + // Workaround for Typescript. Since we're removing the GraphQL API in another PR or two + // 7.6 goes out I don't think it's worth the effort to actually make this + // typecheck pass + const { source } = result.data as any; + const { logEntriesAround } = source; + return { + entries: logEntriesAround.entries, + entriesStart: logEntriesAround.start, + entriesEnd: logEntriesAround.end, + hasMoreAfterEnd: logEntriesAround.hasMoreAfter, + hasMoreBeforeStart: logEntriesAround.hasMoreBefore, + lastLoadedTime: new Date(), + }; +}; + +export const useGraphQLQueries = () => { + const client = useApolloClient(); + if (!client) throw new Error('Unable to get Apollo Client from context'); + return { + getLogEntriesAround: getLogEntries(client, LOAD_CHUNK_SIZE, LOAD_CHUNK_SIZE), + getLogEntriesBefore: getLogEntries(client, LOAD_CHUNK_SIZE, 0), + getLogEntriesAfter: getLogEntries(client, 0, LOAD_CHUNK_SIZE), + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts new file mode 100644 index 0000000000000..3020ad7eb5f84 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -0,0 +1,268 @@ +/* + * 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 { useEffect, useState, useReducer, useCallback } from 'react'; +import createContainer from 'constate'; +import { pick, throttle } from 'lodash'; +import { useGraphQLQueries } from './gql_queries'; +import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; +import { InfraLogEntry } from './types'; + +const DESIRED_BUFFER_PAGES = 2; + +enum Action { + FetchingNewEntries, + FetchingMoreEntries, + ReceiveNewEntries, + ReceiveEntriesBefore, + ReceiveEntriesAfter, + ErrorOnNewEntries, + ErrorOnMoreEntries, +} + +type ReceiveActions = + | Action.ReceiveNewEntries + | Action.ReceiveEntriesBefore + | Action.ReceiveEntriesAfter; + +interface ReceiveEntriesAction { + type: ReceiveActions; + payload: LogEntriesResponse; +} +interface FetchOrErrorAction { + type: Exclude; +} +type ActionObj = ReceiveEntriesAction | FetchOrErrorAction; + +type Dispatch = (action: ActionObj) => void; + +interface LogEntriesProps { + filterQuery: string | null; + timeKey: TimeKey | null; + pagesBeforeStart: number | null; + pagesAfterEnd: number | null; + sourceId: string; + isAutoReloading: boolean; +} + +type FetchEntriesParams = Omit; +type FetchMoreEntriesParams = Pick; + +export interface LogEntriesResponse { + entries: InfraLogEntry[]; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + hasMoreAfterEnd: boolean; + hasMoreBeforeStart: boolean; + lastLoadedTime: Date | null; +} + +export type LogEntriesStateParams = { + isReloading: boolean; + isLoadingMore: boolean; +} & LogEntriesResponse; + +export interface LogEntriesCallbacks { + fetchNewerEntries: () => Promise; +} +export const logEntriesInitialCallbacks = { + fetchNewerEntries: async () => {}, +}; + +export const logEntriesInitialState: LogEntriesStateParams = { + entries: [], + entriesStart: null, + entriesEnd: null, + hasMoreAfterEnd: false, + hasMoreBeforeStart: false, + isReloading: true, + isLoadingMore: false, + lastLoadedTime: null, +}; + +const cleanDuplicateItems = (entriesA: InfraLogEntry[], entriesB: InfraLogEntry[]) => { + const gids = new Set(entriesB.map(item => item.gid)); + return entriesA.filter(item => !gids.has(item.gid)); +}; + +const shouldFetchNewEntries = ({ + prevParams, + timeKey, + filterQuery, + entriesStart, + entriesEnd, +}: FetchEntriesParams & LogEntriesStateParams & { prevParams: FetchEntriesParams }) => { + if (!timeKey) return false; + const shouldLoadWithNewFilter = filterQuery !== prevParams.filterQuery; + const shouldLoadAroundNewPosition = + !entriesStart || !entriesEnd || !timeKeyIsBetween(entriesStart, entriesEnd, timeKey); + return shouldLoadWithNewFilter || shouldLoadAroundNewPosition; +}; + +enum ShouldFetchMoreEntries { + Before, + After, +} + +const shouldFetchMoreEntries = ( + { pagesAfterEnd, pagesBeforeStart }: FetchMoreEntriesParams, + { hasMoreBeforeStart, hasMoreAfterEnd }: LogEntriesStateParams +) => { + if (pagesBeforeStart === null || pagesAfterEnd === null) return false; + if (pagesBeforeStart < DESIRED_BUFFER_PAGES && hasMoreBeforeStart) + return ShouldFetchMoreEntries.Before; + if (pagesAfterEnd < DESIRED_BUFFER_PAGES && hasMoreAfterEnd) return ShouldFetchMoreEntries.After; + return false; +}; + +const useFetchEntriesEffect = ( + state: LogEntriesStateParams, + dispatch: Dispatch, + props: LogEntriesProps +) => { + const { getLogEntriesAround, getLogEntriesBefore, getLogEntriesAfter } = useGraphQLQueries(); + + const [prevParams, cachePrevParams] = useState(props); + const [startedStreaming, setStartedStreaming] = useState(false); + + const runFetchNewEntriesRequest = async () => { + dispatch({ type: Action.FetchingNewEntries }); + try { + const payload = await getLogEntriesAround(props); + dispatch({ type: Action.ReceiveNewEntries, payload }); + } catch (e) { + dispatch({ type: Action.ErrorOnNewEntries }); + } + }; + + const runFetchMoreEntriesRequest = async (direction: ShouldFetchMoreEntries) => { + dispatch({ type: Action.FetchingMoreEntries }); + const getEntriesBefore = direction === ShouldFetchMoreEntries.Before; + const timeKey = getEntriesBefore + ? state.entries[0].key + : state.entries[state.entries.length - 1].key; + const getMoreLogEntries = getEntriesBefore ? getLogEntriesBefore : getLogEntriesAfter; + try { + const payload = await getMoreLogEntries({ ...props, timeKey }); + dispatch({ + type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, + payload, + }); + } catch (e) { + dispatch({ type: Action.ErrorOnMoreEntries }); + } + }; + + const fetchNewEntriesEffectDependencies = Object.values( + pick(props, ['sourceId', 'filterQuery', 'timeKey']) + ); + const fetchNewEntriesEffect = () => { + if (props.isAutoReloading) return; + if (shouldFetchNewEntries({ ...props, ...state, prevParams })) { + runFetchNewEntriesRequest(); + } + cachePrevParams(props); + }; + + const fetchMoreEntriesEffectDependencies = [ + ...Object.values(pick(props, ['pagesAfterEnd', 'pagesBeforeStart'])), + Object.values(pick(state, ['hasMoreBeforeStart', 'hasMoreAfterEnd'])), + ]; + const fetchMoreEntriesEffect = () => { + if (state.isLoadingMore || props.isAutoReloading) return; + const direction = shouldFetchMoreEntries(props, state); + switch (direction) { + case ShouldFetchMoreEntries.Before: + case ShouldFetchMoreEntries.After: + runFetchMoreEntriesRequest(direction); + break; + default: + break; + } + }; + + const fetchNewerEntries = useCallback( + throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), + [props] + ); + + const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore]; + const streamEntriesEffect = () => { + (async () => { + if (props.isAutoReloading && !state.isLoadingMore) { + if (startedStreaming) { + await new Promise(res => setTimeout(res, 5000)); + } else { + setStartedStreaming(true); + } + fetchNewerEntries(); + } else if (!props.isAutoReloading) { + setStartedStreaming(false); + } + })(); + }; + + useEffect(fetchNewEntriesEffect, fetchNewEntriesEffectDependencies); + useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); + useEffect(streamEntriesEffect, streamEntriesEffectDependencies); + + return { fetchNewerEntries }; +}; + +export const useLogEntriesState: ( + props: LogEntriesProps +) => [LogEntriesStateParams, LogEntriesCallbacks] = props => { + const [state, dispatch] = useReducer(logEntriesStateReducer, logEntriesInitialState); + + const { fetchNewerEntries } = useFetchEntriesEffect(state, dispatch, props); + const callbacks = { fetchNewerEntries }; + + return [state, callbacks]; +}; + +const logEntriesStateReducer = (prevState: LogEntriesStateParams, action: ActionObj) => { + switch (action.type) { + case Action.ReceiveNewEntries: + return { ...prevState, ...action.payload, isReloading: false }; + case Action.ReceiveEntriesBefore: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...action.payload.entries, ...prevEntries]; + const { hasMoreBeforeStart, entriesStart, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreBeforeStart, + entriesStart, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.ReceiveEntriesAfter: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...prevEntries, ...action.payload.entries]; + const { hasMoreAfterEnd, entriesEnd, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreAfterEnd, + entriesEnd, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.FetchingNewEntries: + return { ...prevState, isReloading: true }; + case Action.FetchingMoreEntries: + return { ...prevState, isLoadingMore: true }; + case Action.ErrorOnNewEntries: + return { ...prevState, isReloading: false }; + case Action.ErrorOnMoreEntries: + return { ...prevState, isLoadingMore: false }; + default: + throw new Error(); + } +}; + +export const LogEntriesState = createContainer(useLogEntriesState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts new file mode 100644 index 0000000000000..75aea8c415eee --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** A segment of the log entry message that was derived from a field */ +export interface InfraLogMessageFieldSegment { + /** The field the segment was derived from */ + field: string; + /** The segment's message */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} +/** A segment of the log entry message that was derived from a string literal */ +export interface InfraLogMessageConstantSegment { + /** The segment's message */ + constant: string; +} + +export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; + +/** A special built-in column that contains the log entry's timestamp */ +export interface InfraLogEntryTimestampColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The timestamp */ + timestamp: number; +} +/** A special built-in column that contains the log entry's constructed message */ +export interface InfraLogEntryMessageColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** A list of the formatted log entry segments */ + message: InfraLogMessageSegment[]; +} + +/** A column that contains the value of a field of the log entry */ +export interface InfraLogEntryFieldColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The field name of the column */ + field: string; + /** The value of the field in the log entry */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} + +/** A column of a log entry */ +export type InfraLogEntryColumn = + | InfraLogEntryTimestampColumn + | InfraLogEntryMessageColumn + | InfraLogEntryFieldColumn; + +/** A representation of the log entry's position in the event stream */ +export interface InfraTimeKey { + /** The timestamp of the event that the log entry corresponds to */ + time: number; + /** The tiebreaker that disambiguates events with the same timestamp */ + tiebreaker: number; +} + +/** A log entry */ +export interface InfraLogEntry { + /** A unique representation of the log entry's position in the event stream */ + key: InfraTimeKey; + /** The log entry's id */ + gid: string; + /** The source id */ + source: string; + /** The columns used for rendering the log entry */ + columns: InfraLogEntryColumn[]; +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts new file mode 100644 index 0000000000000..a737d19a5923d --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logFilterSelectors as logFilterReduxSelectors } from '../../../store/local/selectors'; + +export const useLogFilterState = () => { + const { local: state } = useContext(ReduxStateContext); + const filterQuery = logFilterReduxSelectors.selectLogFilterQueryAsJson(state); + return { filterQuery }; +}; + +export interface LogFilterStateParams { + filterQuery: string | null; +} + +export const logFilterInitialState = { + filterQuery: null, +}; + +export const LogFilterState = createContainer(useLogFilterState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index 39c088b1ceb5a..fa1ccb4efa4bb 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -6,30 +6,30 @@ import createContainer from 'constate'; import { useState, useContext } from 'react'; - import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; import { useReduxBridgeSetters } from './redux_bridge_setters'; import { useLogSummaryBufferInterval } from '../log_summary'; import { LogViewConfiguration } from '../log_view_configuration'; +import { TimeKey } from '../../../../common/time'; export const useLogHighlightsState = ({ sourceId, sourceVersion, + entriesStart, + entriesEnd, + filterQuery, }: { sourceId: string; sourceVersion: string | undefined; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + filterQuery: string | null; }) => { const [highlightTerms, setHighlightTerms] = useState([]); - const { - startKey, - endKey, - filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, @@ -50,7 +50,14 @@ export const useLogHighlightsState = ({ logEntryHighlights, logEntryHighlightsById, loadLogEntryHighlightsRequest, - } = useLogEntryHighlights(sourceId, sourceVersion, startKey, endKey, filterQuery, highlightTerms); + } = useLogEntryHighlights( + sourceId, + sourceVersion, + entriesStart, + entriesEnd, + filterQuery, + highlightTerms + ); const { logSummaryHighlights, loadLogSummaryHighlightsRequest } = useLogSummaryHighlights( sourceId, @@ -78,8 +85,6 @@ export const useLogHighlightsState = ({ return { highlightTerms, setHighlightTerms, - setStartKey, - setEndKey, setFilterQuery, logEntryHighlights, logEntryHighlightsById, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx index b3254f597dfcf..0e778f35188f0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx @@ -8,19 +8,13 @@ import { useState } from 'react'; import { TimeKey } from '../../../../common/time'; export const useReduxBridgeSetters = () => { - const [startKey, setStartKey] = useState(null); - const [endKey, setEndKey] = useState(null); const [filterQuery, setFilterQuery] = useState(null); const [visibleMidpoint, setVisibleMidpoint] = useState(null); const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined); return { - startKey, - endKey, filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx index 220eaade12fa6..2b60c6edd97aa 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx @@ -8,23 +8,11 @@ import React, { useEffect, useContext } from 'react'; import { TimeKey } from '../../../../common/time'; import { withLogFilter } from '../with_log_filter'; -import { withStreamItems } from '../with_stream_items'; import { withLogPosition } from '../with_log_position'; import { LogHighlightsState } from './log_highlights'; // Bridges Redux container state with Hooks state. Once state is moved fully from // Redux to Hooks this can be removed. -export const LogHighlightsStreamItemsBridge = withStreamItems( - ({ entriesStart, entriesEnd }: { entriesStart: TimeKey | null; entriesEnd: TimeKey | null }) => { - const { setStartKey, setEndKey } = useContext(LogHighlightsState.Context); - useEffect(() => { - setStartKey(entriesStart); - setEndKey(entriesEnd); - }, [entriesStart, entriesEnd]); - - return null; - } -); export const LogHighlightsPositionBridge = withLogPosition( ({ @@ -61,7 +49,6 @@ export const LogHighlightsFilterQueryBridge = withLogFilter( export const LogHighlightsBridge = ({ indexPattern }: { indexPattern: any }) => ( <> - diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts new file mode 100644 index 0000000000000..7cc8050aafd14 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.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 { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors'; +import { TimeKey } from '../../../../common/time'; + +export const useLogPositionState = () => { + const { local: state } = useContext(ReduxStateContext); + const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state); + const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state); + const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state); + return { timeKey, isAutoReloading, ...pages }; +}; + +export interface LogPositionStateParams { + timeKey: TimeKey | null; + pagesAfterEnd: number | null; + pagesBeforeStart: number | null; + isAutoReloading: boolean; +} + +export const logPositionInitialState = { + timeKey: null, + pagesAfterEnd: null, + pagesBeforeStart: null, + isAutoReloading: false, +}; + +export const LogPositionState = createContainer(useLogPositionState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 12117d88f8283..da468b4391e4e 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -4,85 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useContext, useMemo } from 'react'; -import { connect } from 'react-redux'; - +import { useContext, useMemo } from 'react'; import { StreamItem, LogEntryStreamItem } from '../../components/logging/log_text_stream/item'; -import { logEntriesActions, logEntriesSelectors, logPositionSelectors, State } from '../../store'; import { LogEntry, LogEntryHighlight } from '../../utils/log_entry'; -import { PropsOfContainer, RendererFunction } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; +import { RendererFunction } from '../../utils/typed_react'; // deep inporting to avoid a circular import problem import { LogHighlightsState } from './log_highlights/log_highlights'; +import { LogPositionState } from './log_position'; +import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; -export const withStreamItems = connect( - (state: State) => ({ - isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), - isReloading: logEntriesSelectors.selectIsReloadingEntries(state), - isLoadingMore: logEntriesSelectors.selectIsLoadingMoreEntries(state), - wasAutoReloadJustAborted: logPositionSelectors.selectAutoReloadJustAborted(state), - hasMoreBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart(state), - hasMoreAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd(state), - lastLoadedTime: logEntriesSelectors.selectEntriesLastLoadedTime(state), - entries: logEntriesSelectors.selectEntries(state), - entriesStart: logEntriesSelectors.selectEntriesStart(state), - entriesEnd: logEntriesSelectors.selectEntriesEnd(state), - }), - bindPlainActionCreators({ - loadNewerEntries: logEntriesActions.loadNewerEntries, - reloadEntries: logEntriesActions.reloadEntries, - setSourceId: logEntriesActions.setSourceId, - }) -); - -type WithStreamItemsProps = PropsOfContainer; - -export const WithStreamItems = withStreamItems( - ({ - children, - initializeOnMount, - ...props - }: WithStreamItemsProps & { - children: RendererFunction< - WithStreamItemsProps & { +export const WithStreamItems: React.FunctionComponent<{ + children: RendererFunction< + LogEntriesStateParams & + LogEntriesCallbacks & { currentHighlightKey: UniqueTimeKey | null; items: StreamItem[]; } - >; - initializeOnMount: boolean; - }) => { - const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); - const items = useMemo( - () => - props.isReloading && !props.isAutoReloading && !props.wasAutoReloadJustAborted - ? [] - : props.entries.map(logEntry => - createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) - ), - - [ - props.isReloading, - props.isAutoReloading, - props.wasAutoReloadJustAborted, - props.entries, - logEntryHighlightsById, - ] - ); - - useEffect(() => { - if (initializeOnMount && !props.isReloading && !props.isLoadingMore) { - props.reloadEntries(); - } - }, []); - - return children({ - ...props, - currentHighlightKey, - items, - }); - } -); + >; +}> = ({ children }) => { + const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context); + const { isAutoReloading } = useContext(LogPositionState.Context); + const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); + + const items = useMemo( + () => + logEntries.isReloading && !isAutoReloading + ? [] + : logEntries.entries.map(logEntry => + createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) + ), + + [logEntries.entries, logEntryHighlightsById] + ); + + return children({ + ...logEntries, + ...logEntriesCallbacks, + items, + currentHighlightKey, + }); +}; const createLogEntryStreamItem = ( logEntry: LogEntry, @@ -92,23 +54,3 @@ const createLogEntryStreamItem = ( logEntry, highlights, }); - -/** - * This component serves as connection between the state and side-effects - * managed by redux and the state and effects managed by hooks. In particular, - * it forwards changes of the source id to redux via the action creator - * `setSourceId`. - * - * It will be mounted beneath the hierachy level where the redux store and the - * source state are initialized. Once the log entry state and loading - * side-effects have been migrated from redux to hooks it can be removed. - */ -export const ReduxSourceIdBridge = withStreamItems( - ({ setSourceId, sourceId }: { setSourceId: (sourceId: string) => void; sourceId: string }) => { - useEffect(() => { - setSourceId(sourceId); - }, [setSourceId, sourceId]); - - return null; - } -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts similarity index 93% rename from x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts rename to x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts index 78893b83cd645..41ff3c293a713 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts +++ b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts @@ -6,7 +6,7 @@ import gql from 'graphql-tag'; -import { sharedFragments } from '../../../../../common/graphql/shared'; +import { sharedFragments } from '../../common/graphql/shared'; export const logEntriesQuery = gql` query LogEntries( diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx index a54780267f1c1..a7dff2f11f232 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx @@ -8,7 +8,6 @@ import React, { useMemo, useState } from 'react'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx/target'; import { KFetchError } from 'ui/kfetch/kfetch_error'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { useTrackedPromise } from '../utils/use_tracked_promise'; @@ -44,13 +43,13 @@ export function useHTTPRequest( defaultMessage: `Error`, })} - {idx(err.res, r => r.statusText)} ({idx(err.res, r => r.status)}) + {err.res?.statusText} ({err.res?.status})
{i18n.translate('xpack.infra.useHTTPRequest.error.url', { defaultMessage: `URL`, })}
- {idx(err.res, r => r.url)} + {err.res?.url}
), }); 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/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index beb5eb391d368..88212849d4594 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -24,7 +24,7 @@ import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minima import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position'; import { WithLogPosition } from '../../../containers/logs/with_log_position'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; -import { ReduxSourceIdBridge, WithStreamItems } from '../../../containers/logs/with_stream_items'; +import { WithStreamItems } from '../../../containers/logs/with_stream_items'; import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; @@ -44,10 +44,8 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); const derivedIndexPattern = createDerivedIndexPattern('logs'); - return ( <> - @@ -87,7 +85,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { scrollUnlockLiveStreaming, isScrollLocked, }) => ( - + {({ currentHighlightKey, hasMoreAfterEnd, @@ -96,7 +94,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { isReloading, items, lastLoadedTime, - loadNewerEntries, + fetchNewerEntries, }) => ( { items={items} jumpToTarget={jumpToTargetPosition} lastLoadedTime={lastLoadedTime} - loadNewerItems={loadNewerEntries} + loadNewerItems={fetchNewerEntries} reportVisibleInterval={reportVisiblePositions} scale={textScale} target={targetPosition} @@ -140,7 +138,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { visibleMidpointTime, visibleTimeInterval, }) => ( - + {({ isReloading }) => ( { +const LogEntriesStateProvider: React.FC = ({ children }) => { + const { sourceId } = useContext(Source.Context); + const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( + LogPositionState.Context + ); + const { filterQuery } = useContext(LogFilterState.Context); + const entriesProps = { + timeKey, + pagesBeforeStart, + pagesAfterEnd, + filterQuery, + sourceId, + isAutoReloading, + }; + return {children}; +}; + +const LogHighlightsStateProvider: React.FC = ({ children }) => { const { sourceId, version } = useContext(Source.Context); + const [{ entriesStart, entriesEnd }] = useContext(LogEntriesState.Context); + const { filterQuery } = useContext(LogFilterState.Context); + const highlightsProps = { + sourceId, + sourceVersion: version, + entriesStart, + entriesEnd, + filterQuery, + }; + return {children}; +}; +export const LogsPageProviders: React.FunctionComponent = ({ children }) => { return ( - - {children} - + + + + {children} + + + ); diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index 70855101518d6..e2be0d64b8f1e 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -11,4 +11,3 @@ export { waffleTimeActions, waffleOptionsActions, } from './local'; -export { logEntriesActions } from './remote'; diff --git a/x-pack/legacy/plugins/infra/public/store/epics.ts b/x-pack/legacy/plugins/infra/public/store/epics.ts index 4df8e1368ca01..b5e48a4ec6214 100644 --- a/x-pack/legacy/plugins/infra/public/store/epics.ts +++ b/x-pack/legacy/plugins/infra/public/store/epics.ts @@ -7,7 +7,5 @@ import { combineEpics } from 'redux-observable'; import { createLocalEpic } from './local'; -import { createRemoteEpic } from './remote'; -export const createRootEpic = () => - combineEpics(createLocalEpic(), createRemoteEpic()); +export const createRootEpic = () => combineEpics(createLocalEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/epic.ts index 4cfac85f00b15..e1a051355576f 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/epic.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/epic.ts @@ -6,8 +6,6 @@ import { combineEpics } from 'redux-observable'; -import { createLogPositionEpic } from './log_position'; import { createWaffleTimeEpic } from './waffle_time'; -export const createLocalEpic = () => - combineEpics(createLogPositionEpic(), createWaffleTimeEpic()); +export const createLocalEpic = () => combineEpics(createWaffleTimeEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts index 17e06348d18b2..3edb289985d55 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts @@ -8,5 +8,4 @@ import * as logPositionActions from './actions'; import * as logPositionSelectors from './selectors'; export { logPositionActions, logPositionSelectors }; -export * from './epic'; export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts index 3b99e2d4f4379..2ca8be8e40d86 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts @@ -17,8 +17,6 @@ import { unlockAutoReloadScroll, } from './actions'; -import { loadEntriesActionCreators } from '../../remote/log_entries/operations/load'; - interface ManualTargetPositionUpdatePolicy { policy: 'manual'; } @@ -38,9 +36,10 @@ export interface LogPositionState { startKey: TimeKey | null; middleKey: TimeKey | null; endKey: TimeKey | null; + pagesAfterEnd: number; + pagesBeforeStart: number; }; controlsShouldDisplayTargetPosition: boolean; - autoReloadJustAborted: boolean; autoReloadScrollLock: boolean; } @@ -53,9 +52,10 @@ export const initialLogPositionState: LogPositionState = { endKey: null, middleKey: null, startKey: null, + pagesBeforeStart: Infinity, + pagesAfterEnd: Infinity, }, controlsShouldDisplayTargetPosition: false, - autoReloadJustAborted: false, autoReloadScrollLock: false, }; @@ -76,11 +76,16 @@ const targetPositionUpdatePolicyReducer = reducerWithInitialState( const visiblePositionReducer = reducerWithInitialState( initialLogPositionState.visiblePositions -).case(reportVisiblePositions, (state, { startKey, middleKey, endKey }) => ({ - endKey, - middleKey, - startKey, -})); +).case( + reportVisiblePositions, + (state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({ + endKey, + middleKey, + startKey, + pagesBeforeStart, + pagesAfterEnd, + }) +); // Determines whether to use the target position or the visible midpoint when // displaying a timestamp or time range in the toolbar and log minimap. When the @@ -98,17 +103,6 @@ const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( return state; }); -// If auto reload is aborted before a pending request finishes, this flag will -// prevent the UI from displaying the Loading Entries screen -const autoReloadJustAbortedReducer = reducerWithInitialState( - initialLogPositionState.autoReloadJustAborted -) - .case(stopAutoReload, () => true) - .case(startAutoReload, () => false) - .case(loadEntriesActionCreators.resolveDone, () => false) - .case(loadEntriesActionCreators.resolveFailed, () => false) - .case(loadEntriesActionCreators.resolve, () => false); - const autoReloadScrollLockReducer = reducerWithInitialState( initialLogPositionState.autoReloadScrollLock ) @@ -122,6 +116,5 @@ export const logPositionReducer = combineReducers({ updatePolicy: targetPositionUpdatePolicyReducer, visiblePositions: visiblePositionReducer, controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, - autoReloadJustAborted: autoReloadJustAbortedReducer, autoReloadScrollLock: autoReloadScrollLockReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts index 7a2fa86822c56..30fd4d3f77b5c 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts @@ -15,8 +15,6 @@ export const selectIsAutoReloading = (state: LogPositionState) => export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; -export const selectAutoReloadJustAborted = (state: LogPositionState) => state.autoReloadJustAborted; - export const selectFirstVisiblePosition = (state: LogPositionState) => state.visiblePositions.startKey ? state.visiblePositions.startKey : null; @@ -26,6 +24,13 @@ export const selectMiddleVisiblePosition = (state: LogPositionState) => export const selectLastVisiblePosition = (state: LogPositionState) => state.visiblePositions.endKey ? state.visiblePositions.endKey : null; +export const selectPagesBeforeAndAfter = (state: LogPositionState) => + state.visiblePositions + ? { + pagesBeforeStart: state.visiblePositions.pagesBeforeStart, + pagesAfterEnd: state.visiblePositions.pagesAfterEnd, + } + : { pagesBeforeStart: null, pagesAfterEnd: null }; export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) => state.controlsShouldDisplayTargetPosition; diff --git a/x-pack/legacy/plugins/infra/public/store/reducer.ts b/x-pack/legacy/plugins/infra/public/store/reducer.ts index 65b225d019603..2536ddbee401b 100644 --- a/x-pack/legacy/plugins/infra/public/store/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/reducer.ts @@ -7,19 +7,15 @@ import { combineReducers } from 'redux'; import { initialLocalState, localReducer, LocalState } from './local'; -import { initialRemoteState, remoteReducer, RemoteState } from './remote'; export interface State { local: LocalState; - remote: RemoteState; } export const initialState: State = { local: initialLocalState, - remote: initialRemoteState, }; export const reducer = combineReducers({ local: localReducer, - remote: remoteReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts deleted file mode 100644 index 02964bcb27e11..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts +++ /dev/null @@ -1,21 +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 actionCreatorFactory from 'typescript-fsa'; - -import { loadEntriesActionCreators } from './operations/load'; -import { loadMoreEntriesActionCreators } from './operations/load_more'; - -const actionCreator = actionCreatorFactory('x-pack/infra/remote/log_entries'); - -export const setSourceId = actionCreator('SET_SOURCE_ID'); - -export const loadEntries = loadEntriesActionCreators.resolve; -export const loadMoreEntries = loadMoreEntriesActionCreators.resolve; - -export const loadNewerEntries = actionCreator('LOAD_NEWER_LOG_ENTRIES'); - -export const reloadEntries = actionCreator('RELOAD_LOG_ENTRIES'); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts deleted file mode 100644 index 0894a31996042..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts +++ /dev/null @@ -1,198 +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 { Action } from 'redux'; -import { combineEpics, Epic, EpicWithState } from 'redux-observable'; -import { merge } from 'rxjs'; -import { exhaustMap, filter, map, withLatestFrom } from 'rxjs/operators'; - -import { logFilterActions, logPositionActions } from '../..'; -import { pickTimeKey, TimeKey, timeKeyIsBetween } from '../../../../common/time'; -import { - loadEntries, - loadMoreEntries, - loadNewerEntries, - reloadEntries, - setSourceId, -} from './actions'; -import { loadEntriesEpic } from './operations/load'; -import { loadMoreEntriesEpic } from './operations/load_more'; - -const LOAD_CHUNK_SIZE = 200; -const DESIRED_BUFFER_PAGES = 2; - -interface ManageEntriesDependencies { - selectLogEntriesStart: (state: State) => TimeKey | null; - selectLogEntriesEnd: (state: State) => TimeKey | null; - selectHasMoreLogEntriesBeforeStart: (state: State) => boolean; - selectHasMoreLogEntriesAfterEnd: (state: State) => boolean; - selectIsAutoReloadingLogEntries: (state: State) => boolean; - selectIsLoadingLogEntries: (state: State) => boolean; - selectLogFilterQueryAsJson: (state: State) => string | null; - selectVisibleLogMidpointOrTarget: (state: State) => TimeKey | null; -} - -export const createLogEntriesEpic = () => - combineEpics( - createEntriesEffectsEpic(), - loadEntriesEpic as EpicWithState, - loadMoreEntriesEpic as EpicWithState - ); - -export const createEntriesEffectsEpic = (): Epic< - Action, - Action, - State, - ManageEntriesDependencies -> => ( - action$, - state$, - { - selectLogEntriesStart, - selectLogEntriesEnd, - selectHasMoreLogEntriesBeforeStart, - selectHasMoreLogEntriesAfterEnd, - selectIsAutoReloadingLogEntries, - selectIsLoadingLogEntries, - selectLogFilterQueryAsJson, - selectVisibleLogMidpointOrTarget, - } -) => { - const filterQuery$ = state$.pipe(map(selectLogFilterQueryAsJson)); - const visibleMidpointOrTarget$ = state$.pipe( - map(selectVisibleLogMidpointOrTarget), - filter(isNotNull), - map(pickTimeKey) - ); - - const sourceId$ = action$.pipe( - filter(setSourceId.match), - map(({ payload }) => payload) - ); - - const shouldLoadAroundNewPosition$ = action$.pipe( - filter(logPositionActions.jumpToTargetPosition.match), - withLatestFrom(state$), - filter(([{ payload }, state]) => { - const entriesStart = selectLogEntriesStart(state); - const entriesEnd = selectLogEntriesEnd(state); - - return entriesStart && entriesEnd - ? !timeKeyIsBetween(entriesStart, entriesEnd, payload) - : true; - }), - map(([{ payload }]) => pickTimeKey(payload)) - ); - - const shouldLoadWithNewFilter$ = action$.pipe( - filter(logFilterActions.applyLogFilterQuery.match), - withLatestFrom(filterQuery$, (filterQuery, filterQueryString) => filterQueryString) - ); - - const shouldReload$ = merge(action$.pipe(filter(reloadEntries.match)), sourceId$); - - const shouldLoadMoreBefore$ = action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesBeforeStart } }) => pagesBeforeStart < DESIRED_BUFFER_PAGES), - withLatestFrom(state$), - filter( - ([action, state]) => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesBeforeStart(state) - ), - map(([action, state]) => selectLogEntriesStart(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - const shouldLoadMoreAfter$ = merge( - action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesAfterEnd } }) => pagesAfterEnd < DESIRED_BUFFER_PAGES), - withLatestFrom(state$, (action, state) => state), - filter( - state => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesAfterEnd(state) - ) - ), - action$.pipe( - filter(loadNewerEntries.match), - withLatestFrom(state$, (action, state) => state) - ) - ).pipe( - map(state => selectLogEntriesEnd(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - return merge( - shouldLoadAroundNewPosition$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadWithNewFilter$.pipe( - withLatestFrom(visibleMidpointOrTarget$, sourceId$), - exhaustMap(([filterQuery, timeKey, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldReload$.pipe( - withLatestFrom(visibleMidpointOrTarget$, filterQuery$, sourceId$), - exhaustMap(([_, timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreAfter$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: 0, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreBefore$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: 0, - filterQuery, - }), - ]) - ) - ); -}; - -const isNotNull = (value: T | null): value is T => value !== null; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts deleted file mode 100644 index 8e00425526935..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts +++ /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 * as logEntriesActions from './actions'; -import * as logEntriesSelectors from './selectors'; - -export { logEntriesActions, logEntriesSelectors }; -export * from './epic'; -export * from './reducer'; -export { initialLogEntriesState, LogEntriesState } from './state'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts deleted file mode 100644 index ce3193e57ab09..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load'; - -export const loadEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadEntriesActionCreators, - (state, action) => action.payload.result.data.source.logEntriesAround, - () => ({ - entries: [], - hasMoreAfter: false, - hasMoreBefore: false, - }) -); - -export const loadEntriesEpic = createGraphqlQueryEpic(logEntriesQuery, loadEntriesActionCreators); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts deleted file mode 100644 index 7651b039083cf..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts +++ /dev/null @@ -1,72 +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 { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - getLogEntryIndexAfterTime, - getLogEntryIndexBeforeTime, - getLogEntryKey, -} from '../../../../utils/log_entry'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load_more'; - -export const loadMoreEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadMoreEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadMoreEntriesActionCreators, - (state, action) => { - const logEntriesAround = action.payload.result.data.source.logEntriesAround; - const newEntries = logEntriesAround.entries; - const oldEntries = state && state.entries ? state.entries : []; - const oldStart = state && state.start ? state.start : null; - const oldEnd = state && state.end ? state.end : null; - - if (newEntries.length <= 0) { - return state; - } - - if ((action.payload.params.countBefore || 0) > 0) { - const lastLogEntry = newEntries[newEntries.length - 1]; - const prependAtIndex = getLogEntryIndexAfterTime(oldEntries, getLogEntryKey(lastLogEntry)); - return { - start: logEntriesAround.start, - end: oldEnd, - hasMoreBefore: logEntriesAround.hasMoreBefore, - hasMoreAfter: state ? state.hasMoreAfter : logEntriesAround.hasMoreAfter, - entries: [...newEntries, ...oldEntries.slice(prependAtIndex)], - }; - } else if ((action.payload.params.countAfter || 0) > 0) { - const firstLogEntry = newEntries[0]; - const appendAtIndex = getLogEntryIndexBeforeTime(oldEntries, getLogEntryKey(firstLogEntry)); - return { - start: oldStart, - end: logEntriesAround.end, - hasMoreBefore: state ? state.hasMoreBefore : logEntriesAround.hasMoreBefore, - hasMoreAfter: logEntriesAround.hasMoreAfter, - entries: [...oldEntries.slice(0, appendAtIndex), ...newEntries], - }; - } else { - return state; - } - } -); - -export const loadMoreEntriesEpic = createGraphqlQueryEpic( - logEntriesQuery, - loadMoreEntriesActionCreators -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts deleted file mode 100644 index c0d60c4d336de..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import reduceReducers from 'reduce-reducers'; -import { Reducer } from 'redux'; - -import { loadEntriesReducer } from './operations/load'; -import { loadMoreEntriesReducer } from './operations/load_more'; -import { LogEntriesState } from './state'; - -export const logEntriesReducer = reduceReducers( - loadEntriesReducer, - loadMoreEntriesReducer -) as Reducer; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts deleted file mode 100644 index 0306efc334a51..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts +++ /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 { createSelector } from 'reselect'; - -import { createGraphqlStateSelectors } from '../../../utils/remote_state/remote_graphql_state'; -import { LogEntriesRemoteState } from './state'; - -const entriesGraphlStateSelectors = createGraphqlStateSelectors(); - -export const selectEntries = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.entries : [] -); - -export const selectIsLoadingEntries = entriesGraphlStateSelectors.selectIsLoading; - -export const selectIsReloadingEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load' : false -); - -export const selectIsLoadingMoreEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load_more' : false -); - -export const selectEntriesStart = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.start ? data.start : null -); - -export const selectEntriesEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.end ? data.end : null -); - -export const selectHasMoreBeforeStart = createSelector( - entriesGraphlStateSelectors.selectData, - data => (data ? data.hasMoreBefore : true) -); - -export const selectHasMoreAfterEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.hasMoreAfter : true -); - -export const selectEntriesLastLoadedTime = entriesGraphlStateSelectors.selectLoadingResultTime; - -export const selectEntriesStartLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectEntriesEndLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectFirstEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[0] : null -); - -export const selectLastEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[entries.length - 1] : null -); - -export const selectLoadedEntriesTimeInterval = createSelector( - entriesGraphlStateSelectors.selectData, - data => ({ - end: data && data.end ? data.end.time : null, - start: data && data.start ? data.start.time : null, - }) -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts deleted file mode 100644 index 8dbccf6c2bdd3..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.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 { LogEntries as LogEntriesQuery } from '../../../graphql/types'; -import { - createGraphqlInitialState, - GraphqlState, -} from '../../../utils/remote_state/remote_graphql_state'; - -export type LogEntriesRemoteState = LogEntriesQuery.LogEntriesAround; -export type LogEntriesState = GraphqlState; - -export const initialLogEntriesState = createGraphqlInitialState(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts deleted file mode 100644 index 2ab0a9a47ae86..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts +++ /dev/null @@ -1,20 +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 { combineReducers } from 'redux'; -import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries'; - -export interface RemoteState { - logEntries: LogEntriesState; -} - -export const initialRemoteState = { - logEntries: initialLogEntriesState, -}; - -export const remoteReducer = combineReducers({ - logEntries: logEntriesReducer, -}); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts deleted file mode 100644 index b1daa2bc8110d..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts +++ /dev/null @@ -1,14 +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 { globalizeSelectors } from '../../utils/typed_redux'; -import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries'; -import { RemoteState } from './reducer'; - -export const logEntriesSelectors = globalizeSelectors( - (state: RemoteState) => state.logEntries, - innerLogEntriesSelectors -); diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index 79a442789d6dd..aecba1779d036 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createSelector } from 'reselect'; - -import { getLogEntryAtTime } from '../utils/log_entry'; import { globalizeSelectors } from '../utils/typed_redux'; import { logFilterSelectors as localLogFilterSelectors, @@ -16,8 +13,6 @@ import { waffleTimeSelectors as localWaffleTimeSelectors, } from './local'; import { State } from './reducer'; -import { logEntriesSelectors as remoteLogEntriesSelectors } from './remote'; - /** * local selectors */ @@ -29,36 +24,3 @@ export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPosi export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); - -/** - * remote selectors - */ - -const selectRemote = (state: State) => state.remote; - -export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors); - -/** - * shared selectors - */ - -export const sharedSelectors = { - selectFirstVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectFirstVisiblePosition, - (entries, firstVisiblePosition) => - firstVisiblePosition ? getLogEntryAtTime(entries, firstVisiblePosition) : null - ), - selectMiddleVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectMiddleVisiblePosition, - (entries, middleVisiblePosition) => - middleVisiblePosition ? getLogEntryAtTime(entries, middleVisiblePosition) : null - ), - selectLastVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectLastVisiblePosition, - (entries, lastVisiblePosition) => - lastVisiblePosition ? getLogEntryAtTime(entries, lastVisiblePosition) : null - ), -}; diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index d699db6af042e..601db0f56a693 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -12,7 +12,6 @@ import { map } from 'rxjs/operators'; import { createRootEpic, initialState, - logEntriesSelectors, logFilterSelectors, logPositionSelectors, reducer, @@ -38,11 +37,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) const middlewareDependencies = { postToApi$: observableApi.pipe(map(({ post }) => post)), apolloClient$: apolloClient, - selectIsLoadingLogEntries: logEntriesSelectors.selectIsLoadingEntries, - selectLogEntriesEnd: logEntriesSelectors.selectEntriesEnd, - selectLogEntriesStart: logEntriesSelectors.selectEntriesStart, - selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd, - selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, diff --git a/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx new file mode 100644 index 0000000000000..3bd3d31c745a9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx @@ -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 { connect } from 'react-redux'; +import React, { createContext } from 'react'; +import { State, initialState } from '../store'; + +export const ReduxStateContext = createContext(initialState); + +const withRedux = connect((state: State) => state); +export const ReduxStateContextProvider = withRedux(({ children, ...state }) => { + return {children}; +}); diff --git a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts b/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts deleted file mode 100644 index 0cbc94516617b..0000000000000 --- a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts +++ /dev/null @@ -1,214 +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 { ApolloError, ApolloQueryResult } from 'apollo-client'; -import { DocumentNode } from 'graphql'; -import { Action as ReduxAction } from 'redux'; -import { Epic } from 'redux-observable'; -import { from, Observable } from 'rxjs'; -import { catchError, filter, map, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; -import { Action, ActionCreator, actionCreatorFactory, Failure, Success } from 'typescript-fsa'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { createSelector } from 'reselect'; -import { InfraApolloClient } from '../../lib/lib'; -import { - isFailureLoadingResult, - isIdleLoadingProgress, - isRunningLoadingProgress, - isSuccessLoadingResult, - isUninitializedLoadingResult, - LoadingPolicy, - LoadingProgress, - LoadingResult, -} from '../loading_state'; - -export interface GraphqlState { - current: LoadingProgress>; - last: LoadingResult>; - data: State | undefined; -} - -interface OperationInfo { - operationKey: string; - variables: Variables; -} - -type ResolveDonePayload = Success>; -type ResolveFailedPayload = Failure; - -interface OperationActionCreators { - resolve: ActionCreator; - resolveStarted: ActionCreator; - resolveDone: ActionCreator>; - resolveFailed: ActionCreator>; -} - -export const createGraphqlInitialState = (initialData?: State): GraphqlState => ({ - current: { - progress: 'idle', - }, - last: { - result: 'uninitialized', - }, - data: initialData, -}); - -export const createGraphqlOperationActionCreators = ( - stateKey: string, - operationKey: string -): OperationActionCreators => { - const actionCreator = actionCreatorFactory(`x-pack/infra/remote/${stateKey}/${operationKey}`); - - const resolve = actionCreator('RESOLVE'); - const resolveEffect = actionCreator.async>('RESOLVE'); - - return { - resolve, - resolveStarted: resolveEffect.started, - resolveDone: resolveEffect.done, - resolveFailed: resolveEffect.failed, - }; -}; - -export const createGraphqlOperationReducer = ( - operationKey: string, - initialState: GraphqlState, - actionCreators: OperationActionCreators, - reduceSuccess: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state, - reduceFailure: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state -) => - reducerWithInitialState(initialState) - .caseWithAction(actionCreators.resolveStarted, (state, action) => ({ - ...state, - current: { - progress: 'running', - time: Date.now(), - parameters: { - operationKey, - variables: action.payload, - }, - }, - })) - .caseWithAction(actionCreators.resolveDone, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'success', - parameters: { - operationKey, - variables: action.payload.params, - }, - time: Date.now(), - isExhausted: false, - }, - data: reduceSuccess(state.data, action), - })) - .caseWithAction(actionCreators.resolveFailed, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'failure', - reason: `${action.payload}`, - time: Date.now(), - parameters: { - operationKey, - variables: action.payload.params, - }, - }, - data: reduceFailure(state.data, action), - })) - .build(); - -export const createGraphqlQueryEpic = ( - graphqlQuery: DocumentNode, - actionCreators: OperationActionCreators -): Epic< - ReduxAction, - ReduxAction, - any, - { - apolloClient$: Observable; - } -> => (action$, state$, { apolloClient$ }) => - action$.pipe( - filter(actionCreators.resolve.match), - withLatestFrom(apolloClient$), - switchMap(([{ payload: variables }, apolloClient]) => - from( - apolloClient.query({ - query: graphqlQuery, - variables, - fetchPolicy: 'no-cache', - }) - ).pipe( - map(result => actionCreators.resolveDone({ params: variables, result })), - catchError(error => [actionCreators.resolveFailed({ params: variables, error })]), - startWith(actionCreators.resolveStarted(variables)) - ) - ) - ); - -export const createGraphqlStateSelectors = ( - selectState: (parentState: any) => GraphqlState = parentState => parentState -) => { - const selectData = createSelector(selectState, state => state.data); - - const selectLoadingProgress = createSelector(selectState, state => state.current); - const selectLoadingProgressOperationInfo = createSelector(selectLoadingProgress, progress => - isRunningLoadingProgress(progress) ? progress.parameters : null - ); - const selectIsLoading = createSelector(selectLoadingProgress, isRunningLoadingProgress); - const selectIsIdle = createSelector(selectLoadingProgress, isIdleLoadingProgress); - - const selectLoadingResult = createSelector(selectState, state => state.last); - const selectLoadingResultOperationInfo = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.parameters : null - ); - const selectLoadingResultTime = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.time : null - ); - const selectIsUninitialized = createSelector(selectLoadingResult, isUninitializedLoadingResult); - const selectIsSuccess = createSelector(selectLoadingResult, isSuccessLoadingResult); - const selectIsFailure = createSelector(selectLoadingResult, isFailureLoadingResult); - - const selectLoadingState = createSelector( - selectLoadingProgress, - selectLoadingResult, - (loadingProgress, loadingResult) => ({ - current: loadingProgress, - last: loadingResult, - policy: { - policy: 'manual', - } as LoadingPolicy, - }) - ); - - return { - selectData, - selectIsFailure, - selectIsIdle, - selectIsLoading, - selectIsSuccess, - selectIsUninitialized, - selectLoadingProgress, - selectLoadingProgressOperationInfo, - selectLoadingResult, - selectLoadingResultOperationInfo, - selectLoadingResultTime, - selectLoadingState, - }; -}; 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..834c991d5c6a4 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 @@ -5,12 +5,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 +28,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 +60,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { } private async getDataSetsAndModules( - request: InfraFrameworkRequest, + requestContext: RequestHandlerContext, indices: string, timefield: string ): Promise<{ dataSets: string[]; modules: string[] }> { @@ -103,13 +104,14 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { const bucketSelector = (response: InfraDatabaseSearchResponse<{}, DataSetResponse>) => (response.aggregations && response.aggregations.datasets.buckets) || []; - const handleAfterKey = createAfterKeyHandler('body.aggs.datasets.composite.after', input => - idx(input, _ => _.aggregations.datasets.after_key) + const handleAfterKey = createAfterKeyHandler( + 'body.aggs.datasets.composite.after', + input => input?.aggregations?.datasets?.after_key ); 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..95769414832cc 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; +import { RequestHandlerContext } from 'src/core/server'; import { InfraSnapshotGroupbyInput, InfraSnapshotMetricInput, @@ -13,11 +13,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 +46,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; @@ -74,14 +69,15 @@ const bucketSelector = ( response: InfraDatabaseSearchResponse<{}, InfraSnapshotAggregationResponse> ) => (response.aggregations && response.aggregations.nodes.buckets) || []; -const handleAfterKey = createAfterKeyHandler('body.aggregations.nodes.composite.after', input => - idx(input, _ => _.aggregations.nodes.after_key) +const handleAfterKey = createAfterKeyHandler( + 'body.aggregations.nodes.composite.after', + input => input?.aggregations?.nodes?.after_key ); 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/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js index 7b0e42283d5f5..100fa92a39990 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js @@ -22,7 +22,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { return provider({ config, indexPatterns, boolFilter }); }); - return function getSuggestions({ query, selectionStart, selectionEnd }) { + return function getSuggestions({ query, selectionStart, selectionEnd, signal }) { const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr(selectionEnd)}`; let cursorNode; @@ -34,7 +34,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { const { suggestionTypes = [] } = cursorNode; const suggestionsByType = suggestionTypes.map(type => { - return getSuggestionsByType[type](cursorNode); + return getSuggestionsByType[type](cursorNode, signal); }); return Promise.all(suggestionsByType) .then(suggestionsByType => dedup(flatten(suggestionsByType))); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js index 52bf347a94075..88e2fe812faa6 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js @@ -27,14 +27,14 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { suffix, fieldName, nestedPath, - }) { + }, signal) { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields.filter(field => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); const { getSuggestions } = npStart.plugins.data; const suggestionsByField = fields.map(field => { - return getSuggestions(field.indexPatternTitle, field, query, boolFilter).then(data => { + return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then(data => { const quotedValues = data.map(value => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}`); return wrapAsSuggestions(start, end, query, quotedValues); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js index 591833d646360..771984a1233e3 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js @@ -112,7 +112,7 @@ describe('Kuery value suggestions', function () { const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); await getSuggestions({ fieldName, prefix, suffix }); expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined); + expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined, undefined); }); test('should escape quotes in suggestions', async () => { 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/lens/public/indexpattern_plugin/_datapanel.scss b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss index a496cccc42a58..ed39beeb7d088 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/form_control_layout/mixins'; - .lnsInnerIndexPatternDataPanel { width: 100%; height: 100%; @@ -54,4 +52,3 @@ @include euiFormControlLayoutPadding(1, 'right'); @include euiFormControlLayoutPadding(1, 'left'); } - diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts index 03bbddb998b3c..72cbd1b861a05 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts @@ -129,7 +129,19 @@ const sampleIndexPatterns = { }; function indexPatternSavedObject({ id }: { id: keyof typeof sampleIndexPatterns }) { - const pattern = sampleIndexPatterns[id]; + const pattern = { + ...sampleIndexPatterns[id], + fields: [ + ...sampleIndexPatterns[id].fields, + { + name: 'description', + type: 'string', + aggregatable: false, + searchable: true, + esTypes: ['text'], + }, + ], + }; return { id, type: 'index-pattern', @@ -184,6 +196,16 @@ describe('loader', () => { expect(cache).toMatchObject(sampleIndexPatterns); }); + it('should not allow full text fields', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['a', 'b'], + savedObjectsClient: mockClient(), + }); + + expect(cache).toMatchObject(sampleIndexPatterns); + }); + it('should apply field restrictions from typeMeta', async () => { const cache = await loadIndexPatterns({ cache: {}, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 40d9d25869c7a..89d4224a7df14 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -284,10 +284,7 @@ function fromSavedObject( type, title: attributes.title, fields: (JSON.parse(attributes.fields) as IndexPatternField[]) - .filter( - ({ type: fieldType, esTypes }) => - fieldType !== 'string' || (esTypes && esTypes.includes('keyword')) - ) + .filter(({ aggregatable }) => !!aggregatable) .concat(documentField), typeMeta: attributes.typeMeta ? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo) 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/infra/public/store/remote/epic.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts similarity index 58% rename from x-pack/legacy/plugins/infra/public/store/remote/epic.ts rename to x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts index 3b3ff602731cc..761fcd2674df6 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/epic.ts +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createLogEntriesEpic } from './log_entries'; +let docLinks: Record = {}; -export const createRemoteEpic = () => createLogEntriesEpic(); +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/store/reducers/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js similarity index 78% 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/sections/license_dashboard/index.js index 297e28a320810..b8db24e65e3bd 100644 --- a/x-pack/legacy/plugins/license_management/public/store/reducers/index.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licenseManagement } from './licenseManagement'; +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/sections/license_dashboard/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/sections/license_dashboard/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js index 96c727969e2c2..7872202272acf 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/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 { LicenseDashboard } from './license_dashboard'; +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/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index dc0756978010e..876993eed7795 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -21,12 +21,14 @@ export class TOCEntry extends React.Component { state = { displayName: null, + hasLegendDetails: false, shouldShowModal: false }; componentDidMount() { this._isMounted = true; this._updateDisplayName(); + this._loadHasLegendDetails(); } componentWillUnmount() { @@ -35,6 +37,7 @@ export class TOCEntry extends React.Component { componentDidUpdate() { this._updateDisplayName(); + this._loadHasLegendDetails(); } _toggleLayerDetailsVisibility = () => { @@ -45,6 +48,13 @@ export class TOCEntry extends React.Component { } } + async _loadHasLegendDetails() { + const hasLegendDetails = await this.props.layer.hasLegendDetails(); + if (this._isMounted && hasLegendDetails !== this.state.hasLegendDetails) { + this.setState({ hasLegendDetails }); + } + } + async _updateDisplayName() { const label = await this.props.layer.getDisplayName(); if (this._isMounted) { @@ -143,7 +153,7 @@ export class TOCEntry extends React.Component { } _renderDetailsToggle() { - if (!this.props.layer.hasLegendDetails()) { + if (!this.state.hasLegendDetails) { return null; } @@ -223,7 +233,7 @@ export class TOCEntry extends React.Component { } _renderLegendDetails = () => { - if (!this.props.isLegendDetailsOpen || !this.props.layer.hasLegendDetails()) { + if (!this.props.isLegendDetailsOpen || !this.state.hasLegendDetails) { return null; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js index ebb9fc27be149..0c4999ce4e41f 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js @@ -13,6 +13,7 @@ const LAYER_ID = '1'; const mockLayer = { getId: () => { return LAYER_ID; }, + hasLegendDetails: async () => { return true; }, renderLegendDetails: () => { return (
TOC details mock
); }, getDisplayName: () => { return 'layer 1'; }, isVisible: () => { return true; }, 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/heatmap_layer.js b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js index 0342975ce3192..632ed22474022 100644 --- a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js @@ -98,7 +98,7 @@ export class HeatmapLayer extends VectorLayer { return 'heatmap'; } - hasLegendDetails() { + async hasLegendDetails() { return true; } 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..c646d0ac5c1df 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() { @@ -186,7 +186,7 @@ export class AbstractLayer { }; } - hasLegendDetails() { + async hasLegendDetails() { return false; } 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..de1b47ea28a91 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'; @@ -25,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; +import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; const MAX_GEOTILE_LEVEL = 29; @@ -170,13 +171,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({ @@ -214,22 +217,26 @@ export class ESGeoGridSource extends AbstractESAggSource { ]; } - _createDefaultLayerDescriptor(options) { - if (this._descriptor.requestType === RENDER_AS.HEATMAP) { - return HeatmapLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options - }); - } + _createHeatmapLayerDescriptor(options) { + return HeatmapLayer.createDescriptor({ + sourceDescriptor: this._descriptor, + ...options + }); + } + _createVectorLayerDescriptor(options) { const descriptor = VectorLayer.createDescriptor({ 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,16 +245,27 @@ export class ESGeoGridSource extends AbstractESAggSource { color: 'Blues' } }, - [vectorStyles.ICON_SIZE]: { + [VECTOR_STYLES.LINE_COLOR]: { + type: StaticStyleProperty.type, + options: { + color: '#FFF' + } + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: StaticStyleProperty.type, + options: { + size: 0 + } + }, + [VECTOR_STYLES.ICON_SIZE]: { type: DynamicStyleProperty.type, options: { + ...defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options, field: { label: COUNT_PROP_LABEL, name: COUNT_PROP_NAME, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); @@ -257,15 +275,15 @@ export class ESGeoGridSource extends AbstractESAggSource { createDefaultLayer(options) { if (this._descriptor.requestType === RENDER_AS.HEATMAP) { return new HeatmapLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), + layerDescriptor: this._createHeatmapLayerDescriptor(options), source: this }); } - const layerDescriptor = this._createDefaultLayerDescriptor(options); + const layerDescriptor = this._createVectorLayerDescriptor(options); const style = new VectorStyle(layerDescriptor.style, this); return new VectorLayer({ - layerDescriptor: layerDescriptor, + layerDescriptor, source: this, style }); 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..c897ee2a5b2d0 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,16 +137,15 @@ 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, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); @@ -203,13 +204,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..6bd8b64ddf832 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 @@ -16,91 +16,38 @@ const EMPTY_VALUE = ''; export class StylePropertyLegendRow extends Component { - state = { - label: '', - hasLoadedFieldFormatter: false, - } - - componentDidMount() { - this._isMounted = true; - this._prevLabel = undefined; - this._fieldValueFormatter = undefined; - this._loadLabel(); - this._loadFieldFormatter(); - } - - componentDidUpdate() { - // label could change so it needs to be loaded on update - this._loadLabel(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadFieldFormatter() { - if (this.props.style.isDynamic() && this.props.style.isComplete() && this.props.style.getField().getSource()) { - const field = this.props.style.getField(); - const source = field.getSource(); - this._fieldValueFormatter = await source.getFieldFormatter(field.getName()); - } else { - this._fieldValueFormatter = null; - } - if (this._isMounted) { - this.setState({ hasLoadedFieldFormatter: true }); - } - } - - _loadLabel = async () => { - if (this._excludeFromHeader()) { - return; - } - - // have to load label and then check for changes since field name stays constant while label may change - const label = await this.props.style.getField().getLabel(); - if (this._prevLabel === label) { - return; - } - - this._prevLabel = label; - if (this._isMounted) { - this.setState({ label }); - } - } - - _excludeFromHeader() { - return !this.props.style.isDynamic() || !this.props.style.isComplete() || !this.props.style.getField().getName(); - } - _formatValue = value => { - if (!this.state.hasLoadedFieldFormatter || !this._fieldValueFormatter || value === EMPTY_VALUE) { + if (!this.props.fieldFormatter || value === EMPTY_VALUE) { return value; } - return this._fieldValueFormatter(value); + return this.props.fieldFormatter(value); } 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 ( ); } } StylePropertyLegendRow.propTypes = { + label: PropTypes.string, + fieldFormatter: PropTypes.func, range: rangeShape, style: PropTypes.object }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index e339cad6af973..5f4139432a912 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -4,29 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import _ from 'lodash'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { rangeShape } from '../style_option_shapes'; import { StylePropertyLegendRow } from './style_property_legend_row'; -export function VectorStyleLegend({ styleProperties }) { - return styleProperties.map(styleProperty => { - return ( - - ); - }); -} +export class VectorStyleLegend extends Component { + + state = { + rows: [], + } + + componentDidMount() { + this._isMounted = true; + this._prevRowDescriptors = undefined; + this._loadRows(); + } + + componentDidUpdate() { + this._loadRows(); + } -const stylePropertyShape = PropTypes.shape({ - range: rangeShape, - style: PropTypes.object -}); + componentWillUnmount() { + this._isMounted = false; + } + + _loadRows = _.debounce(async () => { + const rows = await this.props.loadRows(); + const rowDescriptors = rows.map(row => { + return { + label: row.label, + range: row.range, + styleOptions: row.style.getOptions(), + }; + }); + if (this._isMounted && !_.isEqual(rowDescriptors, this._prevRowDescriptors)) { + this._prevRowDescriptors = rowDescriptors; + this.setState({ rows }); + } + }, 100); + + render() { + return this.state.rows.map(rowProps => { + return ( + + ); + }); + } +} VectorStyleLegend.propTypes = { - styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired + loadRows: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 4279fbbe0fbb3..1d5815a84920c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -7,21 +7,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ValidatedDualRange } from 'ui/validated_range'; -import { DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE } from '../../vector_style_defaults'; +import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { const onSizeChange = ([min, max]) => { onChange({ - minSize: Math.max(DEFAULT_MIN_SIZE, parseInt(min, 10)), - maxSize: Math.min(DEFAULT_MAX_SIZE, parseInt(max, 10)), + minSize: Math.max(MIN_SIZE, parseInt(min, 10)), + maxSize: Math.min(MAX_SIZE, parseInt(max, 10)), }); }; return ( { + 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..b8fc428a62a52 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,37 @@ export function getComputedFieldName(styleName, fieldName) { export function getComputedFieldNamePrefix(fieldName) { return `__kbn__dynamic__${fieldName}`; } + +export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatureType) { + if (supportedFeatures.length === 1) { + return supportedFeatures[0] === featureType; + } + + if (!hasFeatureType) { + return false; + } + + const featureTypes = Object.keys(hasFeatureType); + return featureTypes.reduce((isOnlyTargetFeatureType, featureTypeKey) => { + const hasFeature = hasFeatureType[featureTypeKey]; + return featureTypeKey === featureType + ? isOnlyTargetFeatureType && hasFeature + : isOnlyTargetFeatureType && !hasFeature; + }, true); +} + +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..1d6e6b256463e --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isOnlySingleFeatureType, scaleValue } from './style_util'; +import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; + +describe('isOnlySingleFeatureType', () => { + describe('source supports single feature type', () => { + const supportedFeatures = [VECTOR_SHAPE_TYPES.POINT]; + + test('Is only single feature type when only supported feature type is target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures)).toBe(true); + }); + + test('Is not single feature type when only supported feature type is not target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures)).toBe(false); + }); + }); + + describe('source supports multiple feature types', () => { + const supportedFeatures = [ + VECTOR_SHAPE_TYPES.POINT, + VECTOR_SHAPE_TYPES.LINE, + VECTOR_SHAPE_TYPES.POLYGON + ]; + + test('Is only single feature type when data only has target feature type', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: false, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(true); + }); + + test('Is not single feature type when data has multiple feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: true, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures, hasFeatureType)).toBe(false); + }); + + test('Is not single feature type when data does not have target feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: false, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(false); + }); + }); +}); + +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_constants.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js index 21d55b9a75991..027d558d3c033 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js @@ -7,4 +7,4 @@ export const SYMBOLIZE_AS_CIRCLE = 'circle'; export const SYMBOLIZE_AS_ICON = 'icon'; -export const DEFAULT_ICON_SIZE = 10; +export const DEFAULT_ICON_SIZE = 6; 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..426af96b63ba2 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, LINE_STYLES, POLYGON_STYLES, 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, isOnlySingleFeatureType, 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 || {}; } @@ -248,36 +271,80 @@ export class VectorStyle extends AbstractStyle { return styleProperties.filter(styleProperty => (styleProperty.isDynamic() && styleProperty.isComplete())); } - _checkIfOnlyFeatureType = async (featureType) => { - const supportedFeatures = await this._source.getSupportedShapeTypes(); - - if (supportedFeatures.length === 1) { - return supportedFeatures[0] === featureType; - } - - if (!this._descriptor.__styleMeta || !this._descriptor.__styleMeta.hasFeatureType) { - return false; - } - - const featureTypes = Object.keys(this._descriptor.__styleMeta.hasFeatureType); - return featureTypes.reduce((isOnlySingleFeatureType, featureTypeKey) => { - const hasFeature = this._descriptor.__styleMeta.hasFeatureType[featureTypeKey]; - return featureTypeKey === featureType - ? isOnlySingleFeatureType && hasFeature - : isOnlySingleFeatureType && !hasFeature; - }, true); + _isOnlySingleFeatureType = async (featureType) => { + return isOnlySingleFeatureType( + featureType, + await this._source.getSupportedShapeTypes(), + this._getStyleMeta().hasFeatureType); } _getIsPointsOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POINT); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT); } _getIsLinesOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.LINE); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE); + } + + _getIsPolygonsOnly = async () => { + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POLYGON); } _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, + }; + } + + _getStyleMeta = () => { + return _.get(this._descriptor, '__styleMeta', {}); } getIcon = () => { @@ -289,28 +356,50 @@ export class VectorStyle extends AbstractStyle { ); } - renderLegendDetails() { - const styles = this._getAllStyleProperties(); - const styleProperties = styles.map((style) => { - return { - // eslint-disable-next-line max-len - range: (style.isDynamic() && style.isComplete() && style.getField().getName()) ? this._getFieldRange(style.getField().getName()) : null, - style: style - }; + async _getLegendDetailStyleProperties() { + const isLinesOnly = await this._getIsLinesOnly(); + const isPolygonsOnly = await this._getIsPolygonsOnly(); + + return this.getDynamicPropertiesArray().filter(styleProperty => { + if (isLinesOnly) { + return LINE_STYLES.includes(styleProperty.getStyleName()); + } + + if (isPolygonsOnly) { + return POLYGON_STYLES.includes(styleProperty.getStyleName()); + } + + return true; }); + } - return ( - - ); + async hasLegendDetails() { + const styles = await this._getLegendDetailStyleProperties(); + return styles.length > 0; + } + + renderLegendDetails() { + const loadRows = async () => { + const styles = await this._getLegendDetailStyleProperties(); + const promises = styles.map(async (style) => { + return { + label: await style.getField().getLabel(), + fieldFormatter: await this._source.getFieldFormatter(style.getField().getName()), + range: this._getFieldRange(style.getField().getName()), + style, + }; + }); + return await Promise.all(promises); + }; + + return ; } _getStyleFields() { @@ -321,7 +410,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 +469,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 +533,6 @@ export class VectorStyle extends AbstractStyle { } _makeField(fieldDescriptor) { - if (!fieldDescriptor || !fieldDescriptor.name) { return null; } @@ -473,8 +555,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..33218e909ff92 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 @@ -14,10 +14,11 @@ import { const DEFAULT_ICON = 'airfield'; -export const DEFAULT_MIN_SIZE = 1; -export const DEFAULT_MAX_SIZE = 64; +export const MIN_SIZE = 1; +export const MAX_SIZE = 64; +export const DEFAULT_SIGMA = 3; -export const vectorStyles = { +export const VECTOR_STYLES = { SYMBOL: 'symbol', FILL_COLOR: 'fillColor', LINE_COLOR: 'lineColor', @@ -26,10 +27,13 @@ export const vectorStyles = { ICON_ORIENTATION: 'iconOrientation' }; +export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; +export const POLYGON_STYLES = [VECTOR_STYLES.FILL_COLOR, VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; + export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), - [vectorStyles.SYMBOL]: { + [VECTOR_STYLES.SYMBOL]: { options: { symbolizeAs: SYMBOLIZE_AS_CIRCLE, symbolId: DEFAULT_ICON, @@ -48,31 +52,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 +87,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, + minSize: 1, + maxSize: 10, 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, + minSize: 4, + maxSize: 32, 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..60a6201ac872c 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; } @@ -142,8 +145,8 @@ export class VectorLayer extends AbstractLayer { return 'vector'; } - hasLegendDetails() { - return this._style.getDynamicPropertiesArray().length > 0; + async hasLegendDetails() { + return this._style.hasLegendDetails(); } renderLegendDetails() { @@ -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/components/chart_tooltip/_chart_tooltip.scss b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss index ea5c245d9e39f..25bf3597c3466 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss +++ b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; - .mlChartTooltip { @include euiToolTipStyle('s'); @include euiFontSizeXS; @@ -50,5 +47,4 @@ &--hidden { opacity: 0; } - } diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/legacy/plugins/ml/public/application/components/job_messages/job_messages.tsx index aedb8b6d17d06..5fb3ab95e4ea0 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; -import { EuiSpacer, EuiBasicTable } from '@elastic/eui'; +import { EuiSpacer, EuiInMemoryTable } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { i18n } from '@kbn/i18n'; @@ -35,11 +35,13 @@ export const JobMessages: FC = ({ messages, loading, error }) width: `${theme.euiSizeL}`, }, { + field: 'timestamp', name: i18n.translate('xpack.ml.jobMessages.timeLabel', { defaultMessage: 'Time', }), - render: (message: any) => formatDate(message.timestamp, TIME_FORMAT), + render: (timestamp: number) => formatDate(timestamp, TIME_FORMAT), width: '120px', + sortable: true, }, { field: 'node_name', @@ -57,13 +59,22 @@ export const JobMessages: FC = ({ messages, loading, error }) }, ]; + const defaultSorting = { + sort: { + field: 'timestamp', + direction: 'asc', + }, + }; + return ( <> - _.regression.mean_squared_error.error) as number; + let meanSquaredError = response?.regression?.mean_squared_error?.error; + if (meanSquaredError) { meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS)); } - let rSquared = idx(response, _ => _.regression.r_squared.value) as number; + let rSquared = response?.regression?.r_squared?.value; if (rSquared) { rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS)); } 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/components/analytics_list/expanded_row.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index a650a867aea60..91b73307ef56c 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -6,7 +6,6 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import moment from 'moment-timezone'; -import { idx } from '@kbn/elastic-idx'; import { EuiIcon, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui'; @@ -64,7 +63,7 @@ export const ExpandedRow: FC = ({ item }) => { const [generalizationEval, setGeneralizationEval] = useState(defaultEval); const [isLoadingTraining, setIsLoadingTraining] = useState(false); const [isLoadingGeneralization, setIsLoadingGeneralization] = useState(false); - const index = idx(item, _ => _.config.dest.index) as string; + const index = item?.config?.dest?.index; const dependentVariable = getDependentVar(item.config.analysis); const predictionFieldName = getPredictionFieldName(item.config.analysis); // default is 'ml' diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx index b85591d13998a..49381e3b1c031 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx @@ -56,6 +56,6 @@ describe('Data Frame Analytics: ', () => { expect(options.at(2).props().value).toBe('regression'); const row2 = euiFormRows.at(1); - expect(row2.find('p').text()).toBe('Enable advanced editor'); + expect(row2.find('EuiSwitch').text()).toBe('Enable advanced editor'); }); }); 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/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 918c42f480e1e..eda80ca64c86f 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { validateIndexPattern } from 'ui/index_patterns'; @@ -43,7 +42,7 @@ export const mmlUnitInvalidErrorMessage = i18n.translate( const getSourceIndexString = (state: State) => { const { jobConfig } = state; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (typeof sourceIndex === 'string') { return sourceIndex; @@ -70,17 +69,17 @@ export const validateAdvancedEditor = (state: State): State => { // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. // If there are no messages, it means the index pattern is valid. let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (sourceIndexNameValid) { if (typeof sourceIndex === 'string') { sourceIndexNameValid = !sourceIndex.includes(','); } if (Array.isArray(sourceIndex)) { - sourceIndexNameValid = !sourceIndex.some(d => d.includes(',')); + sourceIndexNameValid = !sourceIndex.some(d => d?.includes(',')); } } - const destinationIndexName = idx(jobConfig, _ => _.dest.index) || ''; + const destinationIndexName = jobConfig?.dest?.index ?? ''; const destinationIndexNameEmpty = destinationIndexName === ''; const destinationIndexNameValid = isValidIndexName(destinationIndexName); const destinationIndexPatternTitleExists = diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index ec70c54892a0e..547a55da7438b 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { getInitialState, getJobConfigFromFormState } from './state'; describe('useCreateAnalyticsForm', () => { @@ -17,15 +15,15 @@ describe('useCreateAnalyticsForm', () => { const jobConfig = getJobConfigFromFormState(state.form); - expect(idx(jobConfig, _ => _.dest.index)).toBe('the-destination-index'); - expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index'); - expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]); - expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined'); + expect(jobConfig?.dest?.index).toBe('the-destination-index'); + expect(jobConfig?.source?.index).toBe('the-source-index'); + expect(jobConfig?.analyzed_fields?.excludes).toStrictEqual([]); + expect(typeof jobConfig?.analyzed_fields?.includes).toBe('undefined'); // test the conversion of comma-separated Kibana index patterns to ES array based index patterns state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form); - expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([ + expect(jobConfigSourceIndexArray?.source?.index).toStrictEqual([ 'the-source-index-1', 'the-source-index-2', ]); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index cac14e654016f..b2f9442f48edb 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -7,7 +7,6 @@ import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx'; import { SimpleSavedObject } from 'src/core/public'; import { ml } from '../../../../../services/ml_api_service'; @@ -229,9 +228,9 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const indexPatternsMap: SourceIndexMap = {}; const savedObjects = (await kibanaContext.indexPatterns.getCache()) || []; savedObjects.forEach((obj: SimpleSavedObject>) => { - const title = idx(obj, _ => _.attributes.title); + const title = obj?.attributes?.title; if (title !== undefined) { - const id = idx(obj, _ => _.id) || ''; + const id = obj?.id || ''; indexPatternsMap[title] = { label: title, value: id }; } }); diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss index 6137a21d0bd8d..39a87ece68ac9 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss @@ -41,12 +41,13 @@ background-color: #920000; } - .type-other, .unknown { + .type-other, + .unknown { background-color: #bfa180; } // Use euiPanel styling - @include euiPanel($selector: 'card-contents'); + @include euiPanel($selector: '.card-contents'); .card-contents { height: 378px; @@ -68,12 +69,16 @@ padding-bottom: 0px; } - .stat.min, .stat.max, .stat.median { + .stat.min, + .stat.max, + .stat.median { width: 30%; display: inline-block; } - .stat.min.value, .stat.max.value, .stat.median.value { + .stat.min.value, + .stat.max.value, + .stat.median.value { font-size: $euiFontSizeS; @include euiTextTruncate; } 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/components/field_data_card/_field_data_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss index ca7d8e3f31c58..b4fd521f21bec 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss @@ -2,7 +2,6 @@ height: 420px; width: 360px; - // Note the names of these styles need to match the type of the field they are displaying. .boolean { background-color: $euiColorVis5; @@ -36,13 +35,13 @@ background-color: $euiColorVis9; } - .type-other, .unknown { + .type-other, + .unknown { background-color: $euiColorVis6; } - // Use euiPanel styling - @include euiPanel($selector: 'mlFieldDataCard__content'); + @include euiPanel($selector: '.mlFieldDataCard__content'); .mlFieldDataCard__content { @include euiFontSizeS; 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/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 6e768cc301852..d98dcb26ee238 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -40,6 +40,7 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe > +
- +
`; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index a7308514de182..98acb1cfa8045 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -228,6 +228,7 @@ export const CustomUrlEditor: FC = ({ onChange={onLabelChange} isInvalid={isInvalidLabel} compressed + data-test-subj="mlJobCustomUrlLabelInput" /> = ({ job, customUrls, setCust : []; return ( - + = ({ job, customUrls, setCust value={label} isInvalid={isInvalidLabel} onChange={e => onLabelChange(e, index)} + data-test-subj={`mlJobEditCustomUrlLabelInput_${index}`} /> @@ -266,5 +267,5 @@ export const CustomUrlList: FC = ({ job, customUrls, setCust ); }); - return <>{customUrlRows}; + return
{customUrlRows}
; }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index ed2c880c1b65f..c36b4ceed7d57 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -223,7 +223,11 @@ export class CustomUrls extends Component { : true; const addButton = ( - + { ) : ( - + { <> {(!editorOpen || editMode === 'modal') && ( - + ( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const field = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + if (field !== undefined) { detectors = [ { @@ -193,10 +191,9 @@ function processFieldlessAggs(detectors: Detector[]) { export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean { const detectors = job.analysis_config.detectors; - const distinctCountField = idx( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const distinctCountField = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + // if distinctCountField is undefined, and any detectors contain a sparse data function // return true if (distinctCountField === undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx index 796e88a816529..dd287d10ab2c8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/additional_section.tsx @@ -25,21 +25,24 @@ export const AdditionalSection: FC = ({ additionalExpanded, setAdditional buttonContent={ButtonContent} onToggle={setAdditionalExpanded} initialIsOpen={additionalExpanded} + data-test-subj="mlJobWizardToggleAdditionalSettingsSection" > - +
+ - - - - - + + + + + - - - - - - + + + + + + +
); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx index c441d1fe6270c..919972186761a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx @@ -67,7 +67,7 @@ export const CalendarsSelection: FC = () => { - + _.custom_settings.created_by); + const created = job?.custom_settings?.created_by; let page = ''; if (created === CREATED_BY_LABEL.SINGLE_METRIC) { 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/ml/public/application/util/object_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts index 1facc761d6379..589803b33e11c 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index c3216773c1a32..ac3f3fef97c70 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -1,10 +1,6 @@ // Should import both the EUI constants and any Kibana ones that are considered global @import 'src/legacy/ui/public/styles/styling_constants'; -// ML needs EUI card styling till it fully adopts React components -@import '@elastic/eui/src/components/panel/variables'; -@import '@elastic/eui/src/components/panel/mixins'; - // ML has it's own variables for coloring @import 'application/variables'; @@ -16,7 +12,6 @@ // Protect the rest of Kibana from ML generic namespacing // SASSTODO: Prefix ml selectors instead #ml-app { - // App level @import 'application/app'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 3b666cad7d8eb..16328a9cd5e93 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -90,7 +90,7 @@ export function jobAuditMessagesProvider(callWithRequest) { body: { sort: [ - { timestamp: { order: 'asc' } }, + { timestamp: { order: 'desc' } }, { job_id: { order: 'asc' } } ], query diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index bf309c65556a8..6ebc43bb92737 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -40,6 +40,7 @@ Array [
-

Skip if unavailable -

+
@@ -367,6 +370,7 @@ Array [
-

Skip if unavailable -

+
, diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js index b14531d8f6efd..9a3aed548dcc9 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { addIndexPatternType } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternCreationConfig } from './rollup_index_pattern_creation_config'; export function initIndexPatternCreation() { - addIndexPatternType(RollupIndexPatternCreationConfig); + managementSetup.indexPattern.creation.add(RollupIndexPatternCreationConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index 104d632e3ac3d..8448f9da19f45 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -5,32 +5,44 @@ */ import React from 'react'; -import { IndexPatternCreationConfig } from 'ui/management/index_pattern_creation'; +import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; import { RollupPrompt } from './components/rollup_prompt'; import { setHttpClient, getRollupIndices } from '../services/api'; import { i18n } from '@kbn/i18n'; -const rollupIndexPatternTypeName = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'rollup index pattern' }); +const rollupIndexPatternTypeName = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'rollup index pattern' } +); -const rollupIndexPatternButtonText = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Rollup index pattern' }); +const rollupIndexPatternButtonText = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Rollup index pattern' } +); -const rollupIndexPatternButtonDescription = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform limited aggregations against summarized data' }); +const rollupIndexPatternButtonDescription = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform limited aggregations against summarized data' } +); -const rollupIndexPatternNoMatchError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', - { defaultMessage: 'Rollup index pattern error: must match one rollup index' }); +const rollupIndexPatternNoMatchError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', + { defaultMessage: 'Rollup index pattern error: must match one rollup index' } +); -const rollupIndexPatternTooManyMatchesError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', - { defaultMessage: 'Rollup index pattern error: can only match one rollup index' }); +const rollupIndexPatternTooManyMatchesError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', + { defaultMessage: 'Rollup index pattern error: can only match one rollup index' } +); -const rollupIndexPatternIndexLabel = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', - { defaultMessage: 'Rollup' }); +const rollupIndexPatternIndexLabel = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', + { defaultMessage: 'Rollup' } +); export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig { - static key = 'rollup'; + key = 'rollup'; constructor(options) { super({ @@ -60,76 +72,85 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig async getIndexPatternCreationOption(urlHandler) { await this.settingUp; - return this.rollupIndices && this.rollupIndices.length ? { - text: rollupIndexPatternButtonText, - description: rollupIndexPatternButtonDescription, - testSubj: `createRollupIndexPatternButton`, - isBeta: this.isBeta, - onClick: () => { - urlHandler('/management/kibana/index_pattern?type=rollup'); - }, - } : null; + return this.rollupIndices && this.rollupIndices.length + ? { + text: rollupIndexPatternButtonText, + description: rollupIndexPatternButtonDescription, + testSubj: `createRollupIndexPatternButton`, + isBeta: this.isBeta, + onClick: () => { + urlHandler('/management/kibana/index_pattern?type=rollup'); + }, + } + : null; } - isRollupIndex = (indexName) => { + isRollupIndex = indexName => { return this.rollupIndices.includes(indexName); - } + }; getIndexTags(indexName) { - return this.isRollupIndex(indexName) ? [{ - key: this.type, - name: rollupIndexPatternIndexLabel, - }] : []; + return this.isRollupIndex(indexName) + ? [ + { + key: this.type, + name: rollupIndexPatternIndexLabel, + }, + ] + : []; } - checkIndicesForErrors = (indices) => { + checkIndicesForErrors = indices => { this.rollupIndex = null; - if(!indices || !indices.length) { + if (!indices || !indices.length) { return; } const rollupIndices = indices.filter(index => this.isRollupIndex(index.name)); - if(!rollupIndices.length) { + if (!rollupIndices.length) { return [rollupIndexPatternNoMatchError]; - } else if(rollupIndices.length > 1) { + } else if (rollupIndices.length > 1) { return [rollupIndexPatternTooManyMatchesError]; } const rollupIndexName = rollupIndices[0].name; const error = this.rollupIndicesCapabilities[rollupIndexName].error; - if(error) { - const errorMessage = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', { - defaultMessage: 'Rollup index pattern error: {error}', - values: { - error + if (error) { + const errorMessage = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', + { + defaultMessage: 'Rollup index pattern error: {error}', + values: { + error, + }, } - }); + ); return [errorMessage]; } this.rollupIndex = rollupIndexName; - } + }; getIndexPatternMappings = () => { - return this.rollupIndex ? { - type: this.type, - typeMeta: { - params: { - rollup_index: this.rollupIndex, + return this.rollupIndex + ? { + type: this.type, + typeMeta: { + params: { + rollup_index: this.rollupIndex, + }, + aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, }, - aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, - }, - } : {}; - } + } + : {}; + }; renderPrompt = () => { - return ( - - ); - } + return ; + }; getFetchForWildcardOptions = () => { return { @@ -138,5 +159,5 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig rollup_index: this.rollupIndex, }, }; - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js index d5ee0d8c6976e..173c28826436b 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfigRegistry } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternListConfig } from './rollup_index_pattern_list_config'; export function initIndexPatternList() { - IndexPatternListConfigRegistry.register(() => RollupIndexPatternListConfig); + managementSetup.indexPattern.list.add(RollupIndexPatternListConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index 92c3ac20a2819..1d8ae6d425065 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -3,49 +3,59 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfig } from 'ui/management/index_pattern_list'; +import { IndexPatternListConfig } from '../../../../../../src/legacy/core_plugins/management/public'; function isRollup(indexPattern) { - return indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup'); + return ( + indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup') + ); } export class RollupIndexPatternListConfig extends IndexPatternListConfig { - static key = 'rollup'; - - getIndexPatternTags = (indexPattern) => { - return isRollup(indexPattern) ? [{ - key: 'rollup', - name: 'Rollup', - }] : []; - } + key = 'rollup'; + + getIndexPatternTags = indexPattern => { + return isRollup(indexPattern) + ? [ + { + key: 'rollup', + name: 'Rollup', + }, + ] + : []; + }; getFieldInfo = (indexPattern, field) => { - if(!isRollup(indexPattern)) { + if (!isRollup(indexPattern)) { return []; } const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; const fieldAggs = allAggs && Object.keys(allAggs).filter(agg => allAggs[agg][field]); - if(!fieldAggs || !fieldAggs.length) { + if (!fieldAggs || !fieldAggs.length) { return []; } - return ['Rollup aggregations:'].concat(fieldAggs.map(aggName => { - const agg = allAggs[aggName][field]; - switch(aggName) { - case 'date_histogram': - return `${aggName} (interval: ${agg.interval}, ${agg.delay ? `delay: ${agg.delay},` : ''} ${agg.time_zone})`; - break; - case 'histogram': - return `${aggName} (interval: ${agg.interval})`; - default: - return aggName; - } - })); - } - - areScriptedFieldsEnabled = (indexPattern) => { + return ['Rollup aggregations:'].concat( + fieldAggs.map(aggName => { + const agg = allAggs[aggName][field]; + switch (aggName) { + case 'date_histogram': + return `${aggName} (interval: ${agg.interval}, ${ + agg.delay ? `delay: ${agg.delay},` : '' + } ${agg.time_zone})`; + break; + case 'histogram': + return `${aggName} (interval: ${agg.interval})`; + default: + return aggName; + } + }) + ); + }; + + areScriptedFieldsEnabled = indexPattern => { return !isRollup(indexPattern); - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/services/api.js b/x-pack/legacy/plugins/rollup/public/services/api.js index 59b26c4592848..ae9e8756c7efc 100644 --- a/x-pack/legacy/plugins/rollup/public/services/api.js +++ b/x-pack/legacy/plugins/rollup/public/services/api.js @@ -3,15 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import chrome from 'ui/chrome'; let httpClient; -export const setHttpClient = (client) => { +export const setHttpClient = client => { httpClient = client; }; -const apiPrefix = chrome.addBasePath('/api/rollup'); export async function getRollupIndices() { - const response = await httpClient.get(`${apiPrefix}/indices`); - return response.data || {}; + const response = await httpClient.get('/api/rollup/indices'); + return response || {}; } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss index d36a587b9257f..5c35e9a23b8a1 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss @@ -1,5 +1,4 @@ @import '@elastic/eui/src/components/header/variables'; -@import '@elastic/eui/src/components/panel/mixins'; @import 'mixins'; @@ -16,7 +15,6 @@ } } - .prfDevTool__page { height: 100%; display: flex; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss index 09bcddef02cc3..6e2ef4a129397 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss @@ -1,4 +1,3 @@ - @include euiBreakpoint('xs', 's') { .prfDevTool__shardDetailsWrapper { flex-direction: column; @@ -34,7 +33,8 @@ font-size: $euiSize; color: $euiColorMediumShade; } - h1, p { + h1, + p { cursor: default; user-select: none; } @@ -44,7 +44,7 @@ } } -@include euiPanel('prfDevTool__main'); +@include euiPanel('.prfDevTool__main'); @include euiBreakpoint('xs', 's') { .prfDevTool__container { @@ -59,4 +59,3 @@ margin: $euiSize 0; } } - diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index fc83dc64aafab..1d798a4a2bc40 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -110,7 +110,8 @@ export const security = (kibana) => new kibana.Plugin({ } watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => { - if (securityPlugin.__legacyCompat.license.getFeatures().allowRbac) { + const xpackInfo = server.plugins.xpack_main.info; + if (xpackInfo.isAvailable() && xpackInfo.feature('security').isEnabled()) { await securityPlugin.__legacyCompat.registerPrivilegesWithCluster(); } }); 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/index.ts b/x-pack/legacy/plugins/siem/index.ts index fca4a13db8cb5..cf9fffc6a1455 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -29,6 +29,7 @@ import { DEFAULT_SIGNALS_INDEX_KEY, } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; +import { initServerWithKibana } from './server/kibana.index'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const siem = (kibana: any) => { @@ -136,45 +137,29 @@ export const siem = (kibana: any) => { mappings: savedObjectMappings, }, init(server: Server) { - const { - config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, - newPlatform, - plugins, - route, - savedObjects, - } = server; - - const { - env, - coreContext: { logger }, - setup, - } = newPlatform; - const initializerContext = { logger, env }; + const { config, newPlatform, plugins, route } = server; + const { coreContext, env, setup } = newPlatform; + const initializerContext = { ...coreContext, env } as PluginInitializerContext; const serverFacade = { config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, plugins: { alerting: plugins.alerting, - xpack_main: plugins.xpack_main, + elasticsearch: plugins.elasticsearch, spaces: plugins.spaces, }, route: route.bind(server), - savedObjects, }; + // @ts-ignore-next-line: setup.plugins is too loosely typed + plugin(initializerContext).setup(setup.core, setup.plugins); - plugin(initializerContext as PluginInitializerContext).setup( - setup.core, - setup.plugins, - serverFacade - ); + initServerWithKibana(initializerContext, serverFacade); }, config(Joi: Root) { + // See x-pack/plugins/siem/server/config.ts if you're adding another + // value where the configuration has to be duplicated at the moment. + // When we move over to the new platform completely this will be + // removed and only server/config.ts should be used. return Joi.object() .keys({ enabled: Joi.boolean().default(true), 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 }) => (
+
@@ -678,80 +650,66 @@ exports[`Stat Items Component rendering kpis with charts it renders the default show={false} title="KPI UNIQUE_PRIVATE_IPS" > - - -
- - - - -
-
-
+ viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + + + + +
+
diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap index 2522d4d1de084..1c6ff628df1e6 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Subtitle it renders 1`] = ` - - - + + + Test subtitle + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx index 77506f8a466a5..b54f3133de472 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx @@ -16,11 +16,7 @@ jest.mock('../../lib/settings/use_kibana_ui_setting'); describe('Subtitle', () => { test('it renders', () => { - const wrapper = shallow( - - - - ); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/tables/__snapshots__/helpers.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/tables/__snapshots__/helpers.test.tsx.snap index 1e6c5eb89352b..82f447a55ec07 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/__snapshots__/helpers.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/tables/__snapshots__/helpers.test.tsx.snap @@ -1,33 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Table Helpers #getRowItemDraggable it returns correctly against snapshot 1`] = ` - - - + } + key="idPrefix-attrName-item1" + render={[Function]} +/> `; exports[`Table Helpers #getRowItemDraggables it returns correctly against snapshot 1`] = ` - - + , - , - - + `; exports[`Table Helpers #getRowItemOverflow it returns correctly against snapshot 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx index eb06fe8a01d79..9d9087d34a765 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx @@ -27,7 +27,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItem}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); }); test('it returns empty value when rowItem is undefined', () => { @@ -38,7 +38,7 @@ describe('Table Helpers', () => { displayCount: 0, }); const wrapper = mount({rowItem}); - expect(wrapper.text()).toBe(getEmptyValue()); + expect(wrapper.find('DragDropContext').text()).toBe(getEmptyValue()); }); test('it returns empty string value when rowItem is empty', () => { @@ -95,7 +95,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItems}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); }); test('it returns empty value when rowItems is undefined', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx index d16e446a879a6..6c793126efa72 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx @@ -11,7 +11,6 @@ import { EuiGlobalToastListToast as Toast, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; -import { pure } from 'recompose'; import * as React from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; @@ -47,7 +46,7 @@ interface DispatchProps { type OwnProps = ReduxProps & DispatchProps; -const AutoSaveWarningMsgComponent = pure( +const AutoSaveWarningMsgComponent = React.memo( ({ newTimelineModel, setTimelineRangeDatePicker, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx index 03c8fd1951725..b57b343d614a8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx @@ -6,7 +6,6 @@ import { EuiButtonIcon } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import { OnColumnRemoved } from '../../../events'; import { EventsHeadingExtra, EventsLoading } from '../../../styles'; @@ -23,7 +22,7 @@ interface Props { /** Given a `header`, returns the `SortDirection` applicable to it */ -export const CloseButton = pure<{ +export const CloseButton = React.memo<{ columnId: string; onColumnRemoved: OnColumnRemoved; }>(({ columnId, onColumnRemoved }) => ( @@ -40,6 +39,7 @@ export const CloseButton = pure<{ }} /> )); + CloseButton.displayName = 'CloseButton'; export const Actions = React.memo(({ header, onColumnRemoved, sort }) => { @@ -58,4 +58,5 @@ export const Actions = React.memo(({ header, onColumnRemoved, sort }) => ); }); + Actions.displayName = 'Actions'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx index 1298760cff73a..057f751f451ac 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.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 { Pin } from '../../../../pin'; @@ -47,7 +46,7 @@ export interface EventsSelectOption { dropdownDisplay: JSX.Element | string; } -export const DropdownDisplay = pure<{ text: string }>(({ text }) => ( +export const DropdownDisplay = React.memo<{ text: string }>(({ text }) => ( {text} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx index 4f414af74a914..634db2dc52676 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx @@ -7,7 +7,6 @@ import { EuiCheckbox, EuiSuperSelect } from '@elastic/eui'; import { noop } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled, { createGlobalStyle } from 'styled-components'; import { getEventsSelectOptions } from './helpers'; @@ -50,7 +49,7 @@ interface Props { timelineId: string; } -export const EventsSelect = pure(({ checkState, timelineId }) => { +export const EventsSelect = React.memo(({ checkState, timelineId }) => { return (
`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx index 53d94716ba3f3..c56322cc69e0c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx @@ -6,7 +6,6 @@ import { noop } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { OnFilterChange } from '../../../events'; import { ColumnHeader } from '../column_header'; @@ -18,7 +17,7 @@ interface Props { } /** Renders a header's filter, based on the `columnHeaderType` */ -export const Filter = pure(({ header, onFilterChange = noop }) => { +export const Filter = React.memo(({ header, onFilterChange = noop }) => { switch (header.columnHeaderType) { case 'text-filter': return ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap index a7c1f67196451..945a9a7aee698 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap @@ -1,20 +1,66 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderToolTipContent it renders the expected table content 1`] = ` - +

+ + Category + : + + + base + +

+

+ + Field + : + + + @timestamp + +

+

+ + Type + : + + + + + date + + +

+

+ + Description + : + + + Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. -Required field for all events.", - "example": "2016-05-23T08:05:34.853Z", - "id": "@timestamp", - "type": "date", - "width": 190, - } - } -/> +Required field for all events. + +

+ `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx index 4b56b9589f19e..a63ec2bf840a6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx @@ -7,7 +7,6 @@ import { EuiIcon } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { getIconFromType } from '../../../../event_details/helpers'; @@ -36,7 +35,7 @@ const ToolTipTableValue = styled.span` `; ToolTipTableValue.displayName = 'ToolTipTableValue'; -export const HeaderToolTipContent = pure<{ header: ColumnHeader }>(({ header }) => ( +export const HeaderToolTipContent = React.memo<{ header: ColumnHeader }>(({ header }) => ( <> {!isEmpty(header.category) && (

diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx index ae6f8757f98a9..c221829f92d04 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx @@ -6,7 +6,6 @@ import { EuiSelect } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { OnRangeSelected } from '../../../events'; @@ -29,7 +28,7 @@ const SelectContainer = styled.div` SelectContainer.displayName = 'SelectContainer'; /** Renders a time range picker for the MiniMap (e.g. 1 Day, 1 Week...) */ -export const RangePicker = pure(({ selected, onRangeSelected }) => { +export const RangePicker = React.memo(({ selected, onRangeSelected }) => { const onChange = (event: React.ChangeEvent): void => { onRangeSelected(event.target.value); }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/__snapshots__/index.test.tsx.snap index 698572d05e30a..fc4bd7bbd6148 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/__snapshots__/index.test.tsx.snap @@ -1,8 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`TextFilter rendering renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx index 995e5d8f58df3..09672eb9a38fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx @@ -7,7 +7,6 @@ import { EuiFieldText } from '@elastic/eui'; import { noop } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { OnFilterChange } from '../../../events'; @@ -30,7 +29,7 @@ const FieldText = styled(EuiFieldText)<{ minwidth: string }>` FieldText.displayName = 'FieldText'; /** Renders a text-based column filter */ -export const TextFilter = pure( +export const TextFilter = React.memo( ({ columnId, minWidth, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 366cd5d1766db..d370a24cc1d4d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -1,124 +1,240 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Columns it renders the expected columns 1`] = ` - - + + + + + + + + + + + - + > + + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx index 3ca17f4ca1ac6..f5b33296561c7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx @@ -8,7 +8,7 @@ import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { mockTimelineData, TestProviders } from '../../../../mock'; +import { mockTimelineData } from '../../../../mock'; import { defaultHeaders } from '../column_headers/default_headers'; import { columnRenderers } from '../renderers'; @@ -19,16 +19,14 @@ describe('Columns', () => { test('it renders the expected columns', () => { const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.test.ts index 83099c68e4ca9..602b88b7ae6d6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.test.ts @@ -203,28 +203,22 @@ describe('helpers', () => { }); describe('getPinTooltip', () => { - test('it informs the user the event may not be unpinned when the event is pinned and has notes', () => { + test('it indicates the event may NOT be unpinned when `isPinned` is `true` and the event has notes', () => { expect(getPinTooltip({ isPinned: true, eventHasNotes: true })).toEqual( 'This event cannot be unpinned because it has notes' ); }); - test('it tells the user the event is persisted when the event is pinned, but has no notes', () => { - expect(getPinTooltip({ isPinned: true, eventHasNotes: false })).toEqual( - 'This event is persisted with the timeline' - ); + test('it indicates the event is pinned when `isPinned` is `true` and the event does NOT have notes', () => { + expect(getPinTooltip({ isPinned: true, eventHasNotes: false })).toEqual('Pinned event'); }); - test('it tells the user the event is NOT persisted when the event is not pinned, but it has notes', () => { - expect(getPinTooltip({ isPinned: false, eventHasNotes: true })).toEqual( - 'This is event is NOT persisted with the timeline' - ); + test('it indicates the event is NOT pinned when `isPinned` is `false` and the event has notes', () => { + expect(getPinTooltip({ isPinned: false, eventHasNotes: true })).toEqual('Unpinned event'); }); - test('it tells the user the event is NOT persisted when the event is not pinned, and has no notes', () => { - expect(getPinTooltip({ isPinned: false, eventHasNotes: false })).toEqual( - 'This is event is NOT persisted with the timeline' - ); + test('it indicates the event is NOT pinned when `isPinned` is `false` and the event does NOT have notes', () => { + expect(getPinTooltip({ isPinned: false, eventHasNotes: false })).toEqual('Unpinned event'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap index 194ef98920bc2..92816d499b029 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/empty_column_renderer.test.tsx.snap @@ -2,7 +2,7 @@ exports[`empty_column_renderer renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap index 03254490a2ce5..b867e0a788449 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap @@ -2,7 +2,7 @@ exports[`get_column_renderer renders correctly against snapshot 1`] = ` - + + + + + + in + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap index 2271514b20faa..e2eaf1d872b26 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap @@ -2,7 +2,7 @@ exports[`plain_column_renderer rendering renders correctly against snapshot 1`] = ` - +

+ + +
`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/user_host_working_dir.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/user_host_working_dir.test.tsx.snap index 595dfd486281c..d01bb85f565dc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/user_host_working_dir.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/user_host_working_dir.test.tsx.snap @@ -1,12 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UserHostWorkingDir rendering it renders against shallow snapshot 1`] = ` - + + + + + + \\ + + + + + + @ + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_details.test.tsx.snap index f777029f2b48d..cf953ccef8f8e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_details.test.tsx.snap @@ -1,114 +1,130 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`GenericDetails rendering it renders the default AuditAcquiredCredsDetails 1`] = ` - + + + + timelineId="test" + /> + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap index f18bfcc612ea8..8e806fadb7bf8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/generic_file_details.test.tsx.snap @@ -1,10 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`GenericFileDetails rendering it renders the default GenericFileDetails 1`] = ` - - + + + - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/primary_secondary_user_info.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/primary_secondary_user_info.test.tsx.snap index 037ec582ca1a0..bf73f545ce3d5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/primary_secondary_user_info.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/__snapshots__/primary_secondary_user_info.test.tsx.snap @@ -1,11 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UserPrimarySecondary rendering it renders the default PrimarySecondaryUserInfo 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx index 90698cc3bf5c9..585a6527423f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx @@ -49,16 +49,14 @@ describe('GenericDetails', () => { }); test('it returns null for text if the data contains no auditd data', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx index 127d88a2a2c6d..b60d89c857421 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; @@ -38,7 +37,7 @@ interface Props { session: string | null | undefined; } -export const AuditdGenericLine = pure( +export const AuditdGenericLine = React.memo( ({ id, contextId, @@ -112,7 +111,7 @@ interface GenericDetailsProps { timelineId: string; } -export const AuditdGenericDetails = pure( +export const AuditdGenericDetails = React.memo( ({ data, contextId, text, timelineId }) => { const id = data._id; const session: string | null | undefined = get('auditd.session[0]', data); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx index 7630202293f8f..63050d3e7dc9e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx @@ -20,16 +20,14 @@ describe('GenericFileDetails', () => { // I cannot and do not want to use BrowserFields for the mocks for the snapshot tests as they are too heavy const browserFields: BrowserFields = {}; const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -53,17 +51,15 @@ describe('GenericFileDetails', () => { }); test('it returns null for text if the data contains no auditd data', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx index 076c605ecb89f..be84696033d69 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiSpacer, IconType } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; @@ -40,7 +39,7 @@ interface Props { session: string | null | undefined; } -export const AuditdGenericFileLine = pure( +export const AuditdGenericFileLine = React.memo( ({ id, contextId, @@ -131,7 +130,7 @@ interface GenericDetailsProps { timelineId: string; } -export const AuditdGenericFileDetails = pure( +export const AuditdGenericFileDetails = React.memo( ({ data, contextId, text, fileIcon = 'document', timelineId }) => { const id = data._id; const session: string | null | undefined = get('auditd.session[0]', data); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx index a895f68db1d5a..bd350a599bb47 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx @@ -6,7 +6,6 @@ import { EuiFlexGroup } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../../draggables'; @@ -23,7 +22,7 @@ interface Props { secondary: string | null | undefined; } -export const PrimarySecondary = pure(({ contextId, eventId, primary, secondary }) => { +export const PrimarySecondary = React.memo(({ contextId, eventId, primary, secondary }) => { if (nilOrUnSet(primary) && nilOrUnSet(secondary)) { return null; } else if (!nilOrUnSet(primary) && nilOrUnSet(secondary)) { @@ -95,7 +94,7 @@ interface PrimarySecondaryUserInfoProps { secondary: string | null | undefined; } -export const PrimarySecondaryUserInfo = pure( +export const PrimarySecondaryUserInfo = React.memo( ({ contextId, eventId, userName, primary, secondary }) => { if (nilOrUnSet(userName) && nilOrUnSet(primary) && nilOrUnSet(secondary)) { return null; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx index 6d11709cdb63e..3c825e6c931be 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../../draggables'; @@ -25,7 +24,7 @@ interface Props { session: string | null | undefined; } -export const SessionUserHostWorkingDir = pure( +export const SessionUserHostWorkingDir = React.memo( ({ eventId, contextId, hostName, userName, primary, secondary, workingDirectory, session }) => ( <> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx index aaa252fd651f8..7529c0623ef47 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { isNumber, isString } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { Bytes, BYTES_FORMAT } from '../../../bytes'; import { Duration, EVENT_DURATION_FIELD_NAME } from '../../../duration'; @@ -24,7 +23,7 @@ import { MESSAGE_FIELD_NAME, } from './constants'; -export const FormattedFieldValue = pure<{ +export const FormattedFieldValue = React.memo<{ contextId: string; eventId: string; fieldFormat?: string; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx index 9b25d246f7780..0bdecfecd6c59 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../draggables'; @@ -19,8 +18,8 @@ interface Props { workingDirectory: string | null | undefined; } -export const HostWorkingDir = pure(({ contextId, eventId, hostName, workingDirectory }) => { - return ( +export const HostWorkingDir = React.memo( + ({ contextId, eventId, hostName, workingDirectory }) => ( <> (({ contextId, eventId, hostName, worki /> - ); -}); + ) +); HostWorkingDir.displayName = 'HostWorkingDir'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx index 6bdb3463949d6..0904c836c2f30 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx @@ -6,7 +6,6 @@ import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { Ecs } from '../../../../graphql/types'; import { asArrayIfExists } from '../../../../lib/helpers'; @@ -51,7 +50,12 @@ import { NETWORK_TRANSPORT_FIELD_NAME, } from '../../../source_destination/field_names'; -export const NetflowRenderer = pure<{ data: Ecs; timelineId: string }>(({ data, timelineId }) => ( +interface NetflowRendererProps { + data: Ecs; + timelineId: string; +} + +export const NetflowRenderer = React.memo(({ data, timelineId }) => ( { }); test('it returns null if everything is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); test('it returns null if everything is undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx index 440feca8f3e56..29d6c0e7d59c2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../draggables'; @@ -22,7 +21,7 @@ interface Props { processName: string | undefined | null; } -export const ProcessDraggable = pure( +export const ProcessDraggable = React.memo( ({ contextId, endgamePid, @@ -94,7 +93,7 @@ export const ProcessDraggable = pure( ProcessDraggable.displayName = 'ProcessDraggable'; -export const ProcessDraggableWithNonExistentProcess = pure( +export const ProcessDraggableWithNonExistentProcess = React.memo( ({ contextId, endgamePid, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_details.test.tsx.snap index 53fa32038855e..e79b1e254fea3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_details.test.tsx.snap @@ -1,499 +1,102 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SuricataDetails rendering it renders the default SuricataDetails 1`] = ` - + + + + + timelineId="test" + /> + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap index 0177dfd941b36..1691eafc4afaf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_signature.test.tsx.snap @@ -1,10 +1,43 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SuricataSignature rendering it renders the default SuricataSignature 1`] = ` - + + + + + +
+ + Hello + + +
+
+
+
`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx index ff84c6e03748e..20b64661b6a00 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -43,14 +43,12 @@ describe('SuricataDetails', () => { }); test('it returns null for text if the data contains no suricata data', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx index 442e5ae7631e1..35733e5e0b31b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx @@ -7,7 +7,6 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; @@ -23,13 +22,14 @@ const Details = styled.div` Details.displayName = 'Details'; -export const SuricataDetails = pure<{ +export const SuricataDetails = React.memo<{ browserFields: BrowserFields; data: Ecs; timelineId: string; }>(({ data, timelineId }) => { const signature: string | null | undefined = get('suricata.eve.alert.signature[0]', data); const signatureId: number | null | undefined = get('suricata.eve.alert.signature_id[0]', data); + if (signatureId != null && signature != null) { return (
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx index 4b1375b33b086..6fe6523180e58 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -6,7 +6,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { ExternalLinkIcon } from '../../../../external_link_icon'; @@ -18,7 +17,7 @@ const LinkEuiFlexItem = styled(EuiFlexItem)` LinkEuiFlexItem.displayName = 'LinkEuiFlexItem'; -export const SuricataRefs = pure<{ signatureId: number }>(({ signatureId }) => { +export const SuricataRefs = React.memo<{ signatureId: number }>(({ signatureId }) => { const links = getLinksFromSignature(signatureId); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx index 512d02b364b2a..2278ed135e5e2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx @@ -34,8 +34,8 @@ describe('SuricataSignature', () => { describe('Tokens', () => { test('should render empty if tokens are empty', () => { - const wrapper = mountWithIntl(); - expect(wrapper.isEmptyRender()).toBeTruthy(); + const wrapper = shallow(); + expect(wrapper.children().length).toEqual(0); }); test('should render a single if it is present', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx index b7aac4a0da020..b85bef4d5ac36 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -6,7 +6,6 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { DragEffects, DraggableWrapper } from '../../../../drag_and_drop/draggable_wrapper'; @@ -43,7 +42,7 @@ const LinkFlexItem = styled(EuiFlexItem)` LinkFlexItem.displayName = 'LinkFlexItem'; -export const Tokens = pure<{ tokens: string[] }>(({ tokens }) => ( +export const Tokens = React.memo<{ tokens: string[] }>(({ tokens }) => ( <> {tokens.map(token => ( @@ -57,7 +56,7 @@ export const Tokens = pure<{ tokens: string[] }>(({ tokens }) => ( Tokens.displayName = 'Tokens'; -export const DraggableSignatureId = pure<{ id: string; signatureId: number }>( +export const DraggableSignatureId = React.memo<{ id: string; signatureId: number }>( ({ id, signatureId }) => ( ( DraggableSignatureId.displayName = 'DraggableSignatureId'; -export const SuricataSignature = pure<{ +export const SuricataSignature = React.memo<{ contextId: string; id: string; signature: string; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/auth_ssh.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/auth_ssh.test.tsx.snap index b7668fe27eca8..bb0671693978b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/auth_ssh.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/auth_ssh.test.tsx.snap @@ -1,12 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AuthSsh rendering it renders against shallow snapshot 1`] = ` - - - + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_details.test.tsx.snap index 180d1814b4c6e..177a3531e5d7b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_details.test.tsx.snap @@ -1,94 +1,106 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SystemGenericDetails rendering it renders the default SystemGenericDetails 1`] = ` - + + + + timelineId="test" + /> +
`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap index cf61a61982e7b..19b7f8b6f77c6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap @@ -1,10 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SystemGenericFileDetails rendering it renders the default SystemGenericDetails 1`] = ` - - + + + - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/package.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/package.test.tsx.snap index 0bd651df1d35d..a80bc3da8395c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/package.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/__snapshots__/package.test.tsx.snap @@ -1,11 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Package rendering it renders against shallow snapshot 1`] = ` - + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx index c12cdeb694cd3..e42a91b7d7972 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx @@ -7,109 +7,93 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { TestProviders } from '../../../../../mock'; import { AuthSsh } from './auth_ssh'; describe('AuthSsh', () => { describe('rendering', () => { test('it renders against shallow snapshot', () => { const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it returns null if sshSignature and sshMethod are both null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.children().length).toEqual(0); }); test('it returns null if sshSignature and sshMethod are both undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.children().length).toEqual(0); }); test('it returns null if sshSignature is null and sshMethod is undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.children().length).toEqual(0); }); test('it returns null if sshSignature is undefined and sshMethod is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect(wrapper.children().length).toEqual(0); }); test('it returns sshSignature if sshMethod is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.text()).toEqual('[sshSignature-1]'); + expect(wrapper.find('DraggableBadge').prop('value')).toEqual('[sshSignature-1]'); }); test('it returns sshMethod if sshSignature is null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); - expect(wrapper.text()).toEqual('[sshMethod-1]'); + expect(wrapper.find('DraggableBadge').prop('value')).toEqual('[sshMethod-1]'); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx index 0c637c0ee7193..60eab8381e98d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; @@ -17,7 +16,7 @@ interface Props { sshMethod: string | null | undefined; } -export const AuthSsh = pure(({ contextId, eventId, sshSignature, sshMethod }) => ( +export const AuthSsh = React.memo(({ contextId, eventId, sshSignature, sshMethod }) => ( <> {sshSignature != null && ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx index 764a93a7294f4..e3627d0ec918a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; @@ -43,7 +42,7 @@ interface Props { workingDirectory: string | null | undefined; } -export const SystemGenericLine = pure( +export const SystemGenericLine = React.memo( ({ contextId, hostName, @@ -141,7 +140,7 @@ interface GenericDetailsProps { timelineId: string; } -export const SystemGenericDetails = pure( +export const SystemGenericDetails = React.memo( ({ data, contextId, text, timelineId }) => { const id = data._id; const message: string | null = data.message != null ? data.message[0] : null; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx index 308f80429d57e..b5d141b4aa2d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx @@ -20,15 +20,13 @@ describe('SystemGenericFileDetails', () => { // I cannot and do not want to use BrowserFields for the mocks for the snapshot tests as they are too heavy const browserFields: BrowserFields = {}; const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx index 0e75e8de6f9b2..401c1c522ca60 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; @@ -64,7 +63,7 @@ interface Props { workingDirectory: string | null | undefined; } -export const SystemGenericFileLine = pure( +export const SystemGenericFileLine = React.memo( ({ args, contextId, @@ -216,7 +215,7 @@ interface GenericDetailsProps { timelineId: string; } -export const SystemGenericFileDetails = pure( +export const SystemGenericFileDetails = React.memo( ({ data, contextId, showMessage = true, text, timelineId }) => { const id = data._id; const message: string | null = data.message != null ? data.message[0] : null; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx index b88d5ad0bab92..a1121f5f43847 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx @@ -28,31 +28,27 @@ describe('Package', () => { }); test('it returns null if all of the package information is null ', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); test('it returns null if all of the package information is undefined ', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx index dbcd8de7292f0..d87639d2b8d6e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; @@ -18,7 +17,7 @@ interface Props { packageVersion: string | null | undefined; } -export const Package = pure( +export const Package = React.memo( ({ contextId, eventId, packageName, packageSummary, packageVersion }) => { if (packageName != null || packageSummary != null || packageVersion != null) { return ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx index 2ea8c6931f00b..93a4b105ce56e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx @@ -29,33 +29,29 @@ describe('UserHostWorkingDir', () => { }); test('it returns null if userDomain, userName, hostName, and workingDirectory are all null', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); test('it returns null if userDomain, userName, hostName, and workingDirectory are all undefined', () => { - const wrapper = mountWithIntl( - - - + const wrapper = shallow( + ); expect(wrapper.isEmptyRender()).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx index f9ee78aaa5bde..281cfd39bd9d2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import { DraggableBadge } from '../../../draggables'; import { TokensFlexItem } from './helpers'; @@ -23,7 +22,7 @@ interface Props { workingDirectory: string | null | undefined; } -export const UserHostWorkingDir = pure( +export const UserHostWorkingDir = React.memo( ({ contextId, eventId, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index b9b7598b206ad..6fc9145187a36 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` - + + + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 7e97208e06d29..d2d593dcd0138 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -16,14 +15,16 @@ import { ZeekDetails } from './zeek_details'; describe('ZeekDetails', () => { describe('rendering', () => { test('it renders the default ZeekDetails', () => { - const wrapper = shallow( - + const wrapper = mountWithIntl( + + + ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('ZeekDetails'))).toMatchSnapshot(); }); test('it returns zeek.connection if the data does contain zeek.connection data', () => { @@ -126,7 +127,12 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect( + wrapper + .find('ZeekDetails') + .children() + .exists() + ).toBeFalsy(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx index ab518dc5ff77a..1a31e560d7810 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx @@ -6,7 +6,6 @@ import { EuiSpacer } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; @@ -21,15 +20,20 @@ const Details = styled.div` Details.displayName = 'Details'; -export const ZeekDetails = pure<{ browserFields: BrowserFields; data: Ecs; timelineId: string }>( - ({ data, timelineId }) => - data.zeek != null ? ( -
- - - -
- ) : null +interface ZeekDetailsProps { + browserFields: BrowserFields; + data: Ecs; + timelineId: string; +} + +export const ZeekDetails = React.memo(({ data, timelineId }) => + data.zeek != null ? ( +
+ + + +
+ ) : null ); ZeekDetails.displayName = 'ZeekDetails'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index 16ff537540ea3..e442f884a8e4b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -71,7 +71,12 @@ describe('ZeekSignature', () => { describe('#TotalVirusLinkSha', () => { test('should return null if value is null', () => { const wrapper = mountWithIntl(); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect( + wrapper + .find('TotalVirusLinkSha') + .children() + .exists() + ).toBeFalsy(); }); test('should render value', () => { @@ -88,7 +93,12 @@ describe('ZeekSignature', () => { describe('#Link', () => { test('should return null if value is null', () => { const wrapper = mountWithIntl(); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect( + wrapper + .find('Link') + .children() + .exists() + ).toBeFalsy(); }); test('should render value', () => { @@ -111,7 +121,12 @@ describe('ZeekSignature', () => { ); - expect(wrapper.isEmptyRender()).toBeTruthy(); + expect( + wrapper + .find('DraggableZeekElement') + .children() + .exists() + ).toBeFalsy(); }); test('it renders the default ZeekSignature', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 8a63c08bc9e5d..6a6ae4e4e7da5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -7,7 +7,6 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { get } from 'lodash/fp'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { Ecs } from '../../../../../graphql/types'; @@ -64,7 +63,7 @@ export const md5StringRenderer: StringRenderer = (value: string) => `md5: ${valu export const sha1StringRenderer: StringRenderer = (value: string) => `sha1: ${value.substr(0, 7)}...`; -export const DraggableZeekElement = pure<{ +export const DraggableZeekElement = React.memo<{ id: string; field: string; value: string | null | undefined; @@ -108,37 +107,44 @@ export const DraggableZeekElement = pure<{ DraggableZeekElement.displayName = 'DraggableZeekElement'; -export const Link = pure<{ value: string | null | undefined; link?: string | null }>( - ({ value, link }) => { - if (value != null) { - if (link != null) { - return ( - -
- {value} - -
-
- ); - } else { - return ( - -
- - -
-
- ); - } +interface LinkProps { + value: string | null | undefined; + link?: string | null; +} + +export const Link = React.memo(({ value, link }) => { + if (value != null) { + if (link != null) { + return ( + +
+ {value} + +
+
+ ); } else { - return null; + return ( + +
+ + +
+
+ ); } + } else { + return null; } -); +}); Link.displayName = 'Link'; -export const TotalVirusLinkSha = pure<{ value: string | null | undefined }>(({ value }) => +interface TotalVirusLinkShaProps { + value: string | null | undefined; +} + +export const TotalVirusLinkSha = React.memo(({ value }) => value != null ? (
@@ -188,7 +194,12 @@ export const extractStateValue = (state: string | null | undefined): string | nu export const constructDroppedValue = (dropped: boolean | null | undefined): string | null => dropped != null ? String(dropped) : null; -export const ZeekSignature = pure<{ data: Ecs; timelineId: string }>(({ data, timelineId }) => { +interface ZeekSignatureProps { + data: Ecs; + timelineId: string; +} + +export const ZeekSignature = React.memo(({ data, timelineId }) => { const id = `zeek-signature-draggable-zeek-element-${timelineId}-${data._id}`; const sessionId: string | null | undefined = get('zeek.session_id[0]', data); const dataSet: string | null | undefined = get('event.dataset[0]', data); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap index 9d96afc811178..5674c18010f67 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SortIndicator rendering renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx index d9b642c5188ec..fc77bbd725704 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx @@ -6,7 +6,6 @@ import { EuiIcon } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import { Direction } from '../../../../graphql/types'; @@ -38,7 +37,7 @@ interface Props { } /** Renders a sort indicator */ -export const SortIndicator = pure(({ sortDirection }) => ( +export const SortIndicator = React.memo(({ sortDirection }) => ( )); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/translations.ts index 40e94cb099644..03efc26bbc9b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/translations.ts @@ -21,11 +21,11 @@ export const COPY_TO_CLIPBOARD = i18n.translate( ); export const UNPINNED = i18n.translate('xpack.siem.timeline.body.pinning.unpinnedTooltip', { - defaultMessage: 'This is event is NOT persisted with the timeline', + defaultMessage: 'Unpinned event', }); export const PINNED = i18n.translate('xpack.siem.timeline.body.pinning.pinnedTooltip', { - defaultMessage: 'This event is persisted with the timeline', + defaultMessage: 'Pinned event', }); export const PINNED_WITH_NOTES = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/data_providers.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/data_providers.test.tsx.snap index edaf5e0459b49..24b034d893d6b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/data_providers.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/data_providers.test.tsx.snap @@ -1,149 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DataProviders rendering renders correctly against snapshot 1`] = ` - + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/empty.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/empty.test.tsx.snap index 1100e1307a7f1..5b4405b8d3bc7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/empty.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/empty.test.tsx.snap @@ -1,7 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Empty rendering renders correctly against snapshot 1`] = ` - - - + + + + Drop anything + + + + highlighted + + + + + + here to build an + + + + query + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap index 4bd97dfee5d40..16094c585911b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap @@ -1,21 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Provider rendering renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/providers.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/providers.test.tsx.snap index 313538306655f..b344381f99d4f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/providers.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/__snapshots__/providers.test.tsx.snap @@ -1,152 +1,514 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Providers rendering renders correctly against snapshot 1`] = ` - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Drop here + + to build an + + OR + + query + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx index 9c5b8fb30fd61..c249e263d1205 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx @@ -14,11 +14,7 @@ import { TestProviders } from '../../../mock/test_providers'; describe('Empty', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow( - - - - ); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index 3ef7240ee0375..87d45f6d3db17 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -6,7 +6,6 @@ import { EuiBadge, EuiBadgeProps, EuiText } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; @@ -88,7 +87,7 @@ interface Props { /** * Prompts the user to drop anything with a facet count into the data providers section. */ -export const Empty = pure(({ showSmallMsg = false }) => ( +export const Empty = React.memo(({ showSmallMsg = false }) => ( `${droppableTimelineProvidersPref * the user to drop anything with a facet count into * the data pro section. */ -export const DataProviders = pure( +export const DataProviders = React.memo( ({ browserFields, id, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.tsx index 7b573b19e2fd4..8fd164eb8a3e2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.tsx @@ -6,7 +6,6 @@ import { noop } from 'lodash/fp'; import React from 'react'; -import { pure } from 'recompose'; import { DataProvider, IS_OPERATOR } from './data_provider'; import { ProviderItemBadge } from './provider_item_badge'; @@ -15,7 +14,7 @@ interface OwnProps { dataProvider: DataProvider; } -export const Provider = pure(({ dataProvider }) => ( +export const Provider = React.memo(({ dataProvider }) => ( ( +export const ProviderBadge = React.memo( ({ deleteProvider, field, isEnabled, isExcluded, operator, providerId, togglePopover, val }) => { const deleteFilter: React.MouseEventHandler = ( event: React.MouseEvent diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx index 0d3874943d2ed..17457b900f3a9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx @@ -7,7 +7,6 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { rgba } from 'polished'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; @@ -78,7 +77,7 @@ interface ProviderItemDropProps { timelineId: string; } -export const ProviderItemAndDragDrop = pure( +export const ProviderItemAndDragDrop = React.memo( ({ browserFields, dataProvider, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx index 8a99ed7417a63..d6e092550473d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx @@ -21,19 +21,17 @@ describe('Providers', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - - - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx index 5a8654509fa88..4d095485ef69d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx @@ -7,7 +7,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormHelpText } from '@elastic/eui'; import * as React from 'react'; import { Draggable } from 'react-beautiful-dnd'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { @@ -112,7 +111,7 @@ export const getDraggableId = ({ id, dataProviderId }: GetDraggableIdParams): st * 2) temporarily disabling a data provider * 3) applying boolean negation to the data provider */ -export const Providers = pure( +export const Providers = React.memo( ({ browserFields, id, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx index 5cef75ed39514..978386e611808 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx @@ -78,24 +78,6 @@ ServerSideEventCount.displayName = 'ServerSideEventCount'; /** The height of the footer, exported for use in height calculations */ export const footerHeight = 40; // px -interface FooterProps { - compact: boolean; - getUpdatedAt: () => number; - hasNextPage: boolean; - height: number; - isEventViewer?: boolean; - isLive: boolean; - isLoading: boolean; - itemsCount: number; - itemsPerPage: number; - itemsPerPageOptions: number[]; - nextCursor: string; - onChangeItemsPerPage: OnChangeItemsPerPage; - onLoadMore: OnLoadMore; - serverSideEventCount: number; - tieBreaker: string; -} - /** Displays the server-side count of events */ export const EventsCountComponent = ({ closePopover, @@ -190,6 +172,24 @@ export const PagingControl = React.memo(PagingControlComponent); PagingControl.displayName = 'PagingControl'; +interface FooterProps { + compact: boolean; + getUpdatedAt: () => number; + hasNextPage: boolean; + height: number; + isEventViewer?: boolean; + isLive: boolean; + isLoading: boolean; + itemsCount: number; + itemsPerPage: number; + itemsPerPageOptions: number[]; + nextCursor: string; + onChangeItemsPerPage: OnChangeItemsPerPage; + onLoadMore: OnLoadMore; + serverSideEventCount: number; + tieBreaker: string; +} + /** Renders a loading indicator and paging controls */ export const FooterComponent = ({ compact, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx index a17e5a6da6331..06ece50690c09 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/last_updated.tsx @@ -7,7 +7,6 @@ import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import React, { useEffect, useState } from 'react'; -import { pure } from 'recompose'; import * as i18n from './translations'; @@ -16,7 +15,7 @@ interface LastUpdatedAtProps { updatedAt: number; } -export const Updated = pure<{ date: number; prefix: string; updatedAt: number }>( +export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>( ({ date, prefix, updatedAt }) => ( <> {prefix} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index c60410bbd2863..0e222f470f0d7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -19,7 +19,6 @@ import { EuiToolTip, } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import uuid from 'uuid'; import { Note } from '../../../lib/note'; @@ -47,7 +46,7 @@ type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean } type UpdateTitle = ({ id, title }: { id: string; title: string }) => void; type UpdateDescription = ({ id, description }: { id: string; description: string }) => void; -export const StarIcon = pure<{ +export const StarIcon = React.memo<{ isFavorite: boolean; timelineId: string; updateIsFavorite: UpdateIsFavorite; @@ -70,61 +69,73 @@ export const StarIcon = pure<{ )); StarIcon.displayName = 'StarIcon'; -export const Description = pure<{ +interface DescriptionProps { description: string; timelineId: string; updateDescription: UpdateDescription; -}>(({ description, timelineId, updateDescription }) => ( - - - updateDescription({ id: timelineId, description: e.target.value })} - placeholder={i18n.DESCRIPTION} - spellCheck={true} - value={description} - /> - - -)); -Description.displayName = 'Description'; +} -export const Name = pure<{ timelineId: string; title: string; updateTitle: UpdateTitle }>( - ({ timelineId, title, updateTitle }) => ( - - updateTitle({ id: timelineId, title: e.target.value })} - placeholder={i18n.UNTITLED_TIMELINE} - spellCheck={true} - value={title} - /> +export const Description = React.memo( + ({ description, timelineId, updateDescription }) => ( + + + updateDescription({ id: timelineId, description: e.target.value })} + placeholder={i18n.DESCRIPTION} + spellCheck={true} + value={description} + /> + ) ); +Description.displayName = 'Description'; + +interface NameProps { + timelineId: string; + title: string; + updateTitle: UpdateTitle; +} + +export const Name = React.memo(({ timelineId, title, updateTitle }) => ( + + updateTitle({ id: timelineId, title: e.target.value })} + placeholder={i18n.UNTITLED_TIMELINE} + spellCheck={true} + value={title} + /> + +)); Name.displayName = 'Name'; -export const NewTimeline = pure<{ +interface NewTimelineProps { createTimeline: CreateTimeline; onClosePopover: () => void; timelineId: string; -}>(({ createTimeline, onClosePopover, timelineId }) => ( - { - createTimeline({ id: timelineId, show: true }); - onClosePopover(); - }} - > - {i18n.NEW_TIMELINE} - -)); +} + +export const NewTimeline = React.memo( + ({ createTimeline, onClosePopover, timelineId }) => ( + { + createTimeline({ id: timelineId, show: true }); + onClosePopover(); + }} + > + {i18n.NEW_TIMELINE} + + ) +); NewTimeline.displayName = 'NewTimeline'; interface NotesButtonProps { @@ -142,47 +153,54 @@ interface NotesButtonProps { const getNewNoteId = (): string => uuid.v4(); -const LargeNotesButton = pure<{ noteIds: string[]; text?: string; toggleShowNotes: () => void }>( - ({ noteIds, text, toggleShowNotes }) => ( - toggleShowNotes()} - size="m" - > - - - - - - {text && text.length ? {text} : null} - - - - {noteIds.length} - - - - - ) -); +interface LargeNotesButtonProps { + noteIds: string[]; + text?: string; + toggleShowNotes: () => void; +} + +const LargeNotesButton = React.memo(({ noteIds, text, toggleShowNotes }) => ( + toggleShowNotes()} + size="m" + > + + + + + + {text && text.length ? {text} : null} + + + + {noteIds.length} + + + + +)); LargeNotesButton.displayName = 'LargeNotesButton'; -const SmallNotesButton = pure<{ noteIds: string[]; toggleShowNotes: () => void }>( - ({ noteIds, toggleShowNotes }) => ( - toggleShowNotes()} - /> - ) -); +interface SmallNotesButtonProps { + noteIds: string[]; + toggleShowNotes: () => void; +} + +const SmallNotesButton = React.memo(({ noteIds, toggleShowNotes }) => ( + toggleShowNotes()} + /> +)); SmallNotesButton.displayName = 'SmallNotesButton'; /** * The internal implementation of the `NotesButton` */ -const NotesButtonComponent = pure( +const NotesButtonComponent = React.memo( ({ animate = true, associateNote, @@ -220,7 +238,7 @@ const NotesButtonComponent = pure( ); NotesButtonComponent.displayName = 'NotesButtonComponent'; -export const NotesButton = pure( +export const NotesButton = React.memo( ({ animate = true, associateNote, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx index 373d9a50d245b..b645202ab4c54 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx @@ -6,7 +6,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiToolTip } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled, { createGlobalStyle } from 'styled-components'; import { esFilters, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; @@ -84,7 +83,7 @@ const ModeFlexItem = styled(EuiFlexItem)` ModeFlexItem.displayName = 'ModeFlexItem'; -export const SearchOrFilter = pure( +export const SearchOrFilter = React.memo( ({ applyKqlFilterQuery, browserFields, diff --git a/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx index 1db521d76d522..07ea165fcbb5c 100644 --- a/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/with_hover_actions/index.tsx @@ -5,7 +5,6 @@ */ import React, { useState, useCallback } from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; interface Props { @@ -36,11 +35,13 @@ const HoverActionsPanelContainer = styled.div` HoverActionsPanelContainer.displayName = 'HoverActionsPanelContainer'; -const HoverActionsPanel = pure<{ children: JSX.Element; show: boolean }>(({ children, show }) => ( - - {show ? children : null} - -)); +const HoverActionsPanel = React.memo<{ children: JSX.Element; show: boolean }>( + ({ children, show }) => ( + + {show ? children : null} + + ) +); HoverActionsPanel.displayName = 'HoverActionsPanel'; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap index e5311bfb050a3..1eef27bcc0a87 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap @@ -1,47 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`WrapperPage it renders 1`] = ` - - -

- Test page -

-
-
+ +

+ Test page +

+
`; exports[`WrapperPage restrict width custom max width when restrictWidth is number 1`] = ` - - -

- Test page -

-
-
+ +

+ Test page +

+
`; exports[`WrapperPage restrict width custom max width when restrictWidth is string 1`] = ` - - -

- Test page -

-
-
+ +

+ Test page +

+
`; exports[`WrapperPage restrict width default max width when restrictWidth is true 1`] = ` - - -

- Test page -

-
-
+ +

+ Test page +

+
`; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx index 95e80e8b9e5de..3ea243fe5cfe7 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -24,7 +24,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); }); describe('restrict width', () => { @@ -37,7 +37,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); }); test('custom max width when restrictWidth is number', () => { @@ -49,7 +49,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); }); test('custom max width when restrictWidth is string', () => { @@ -61,7 +61,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx index 309693427459e..d777c8d6c9eab 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx @@ -27,9 +27,10 @@ const Wrapper = styled.div` } `} `; + Wrapper.displayName = 'Wrapper'; -export interface WrapperPageProps { +interface WrapperPageProps { children: React.ReactNode; className?: string; restrictWidth?: boolean | number | string; @@ -60,4 +61,5 @@ export const WrapperPage = React.memo( ); } ); + WrapperPage.displayName = 'WrapperPage'; diff --git a/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx b/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx index d2494f34ad087..003032493fca2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx @@ -8,7 +8,6 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import { pure } from 'recompose'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; @@ -39,7 +38,7 @@ export interface IpOverviewProps extends QueryTemplateProps { ip: string; } -const IpOverviewComponentQuery = pure( +const IpOverviewComponentQuery = React.memo( ({ id = ID, isInspected, children, filterQuery, skip, sourceId, ip }) => ( query={ipOverviewQuery} diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx index 0d87c0ab9a1c2..20ed7fa991d15 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx @@ -8,7 +8,6 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import { pure } from 'recompose'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; @@ -37,7 +36,7 @@ export interface KpiHostDetailsReducer { isInspected: boolean; } -const KpiHostDetailsComponentQuery = pure( +const KpiHostDetailsComponentQuery = React.memo( ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( query={kpiHostDetailsQuery} diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx index 77575f0137af0..f8aa8aa38e8e1 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx @@ -8,9 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; - import chrome from 'ui/chrome'; -import { pure } from 'recompose'; + import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetKpiHostsQuery, KpiHostsData } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; @@ -37,7 +36,7 @@ export interface KpiHostsProps extends QueryTemplateProps { children: (args: KpiHostsArgs) => React.ReactNode; } -const KpiHostsComponentQuery = pure( +const KpiHostsComponentQuery = React.memo( ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( query={kpiHostsQuery} diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx index e48ea19ba11ef..269c3593d4d73 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx @@ -8,9 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import { pure } from 'recompose'; - import chrome from 'ui/chrome'; + import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetKpiNetworkQuery, KpiNetworkData } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; @@ -37,7 +36,7 @@ export interface KpiNetworkProps extends QueryTemplateProps { children: (args: KpiNetworkArgs) => React.ReactNode; } -const KpiNetworkComponentQuery = pure( +const KpiNetworkComponentQuery = React.memo( ({ id = ID, children, filterQuery, isInspected, skip, sourceId, startDate, endDate }) => ( query={kpiNetworkQuery} diff --git a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx index 4659d063ca1fd..6ef0ee7b33589 100644 --- a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx @@ -8,9 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import { pure } from 'recompose'; - import chrome from 'ui/chrome'; + import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { GetOverviewHostQuery, OverviewHostData } from '../../../graphql/types'; import { inputsModel, inputsSelectors } from '../../../store/inputs'; @@ -41,7 +40,7 @@ export interface OverviewHostProps extends QueryTemplateProps { startDate: number; } -const OverviewHostComponentQuery = pure( +const OverviewHostComponentQuery = React.memo( ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => ( query={overviewHostQuery} diff --git a/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx b/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx index a292bd788b9eb..677d96c10eee2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx @@ -8,7 +8,6 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import { pure } from 'recompose'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; @@ -41,37 +40,37 @@ export interface OverviewNetworkProps extends QueryTemplateProps { startDate: number; } -export const OverviewNetworkComponentQuery = pure( - ({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => ( - - query={overviewNetworkQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const overviewNetwork = getOr({}, `source.OverviewNetwork`, data); - return children({ - id, - inspect: getOr(null, 'source.OverviewNetwork.inspect', data), - overviewNetwork, - loading, - refetch, - }); - }} - - ) -); +export const OverviewNetworkComponentQuery = React.memo< + OverviewNetworkProps & OverviewNetworkReducer +>(({ id = ID, children, filterQuery, isInspected, sourceId, startDate, endDate }) => ( + + query={overviewNetworkQuery} + fetchPolicy={getDefaultFetchPolicy()} + notifyOnNetworkStatusChange + variables={{ + sourceId, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + filterQuery: createFilter(filterQuery), + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + inspect: isInspected, + }} + > + {({ data, loading, refetch }) => { + const overviewNetwork = getOr({}, `source.OverviewNetwork`, data); + return children({ + id, + inspect: getOr(null, 'source.OverviewNetwork.inspect', data), + overviewNetwork, + loading, + refetch, + }); + }} + +)); OverviewNetworkComponentQuery.displayName = 'OverviewNetworkComponentQuery'; diff --git a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx index 894f1fcdea373..c392c2511551f 100644 --- a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx @@ -5,7 +5,6 @@ */ import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import { Clipboard } from './clipboard'; @@ -23,7 +22,7 @@ WithCopyToClipboardContainer.displayName = 'WithCopyToClipboardContainer'; * Renders `children` with an adjacent icon that when clicked, copies `text` to * the clipboard and displays a confirmation toast */ -export const WithCopyToClipboard = pure<{ text: string; titleSummary?: string }>( +export const WithCopyToClipboard = React.memo<{ text: string; titleSummary?: string }>( ({ text, titleSummary, children }) => ( <>{children} diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index e573afaafc2d2..d4c06d998c5a2 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -13,7 +13,6 @@ import * as React from 'react'; import { ApolloProvider } from 'react-apollo'; import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful-dnd'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { pure } from 'recompose'; import { Store } from 'redux'; import { BehaviorSubject } from 'rxjs'; import { ThemeProvider } from 'styled-components'; @@ -86,7 +85,7 @@ Object.defineProperty(window, 'localStorage', { }); /** A utility for wrapping children in the providers required to run most tests */ -export const TestProviders = pure( +export const TestProviders = React.memo( ({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => ( @@ -102,7 +101,7 @@ export const TestProviders = pure( ) ); -export const TestProviderWithoutDragAndDrop = pure( +export const TestProviderWithoutDragAndDrop = React.memo( ({ children, store = createStore(state, apolloClientObservable) }) => ( {children} diff --git a/x-pack/legacy/plugins/siem/public/pages/404.tsx b/x-pack/legacy/plugins/siem/public/pages/404.tsx index f806a5a7fcdd3..ba1cb4f40cbed 100644 --- a/x-pack/legacy/plugins/siem/public/pages/404.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/404.tsx @@ -5,12 +5,11 @@ */ import React from 'react'; -import { pure } from 'recompose'; import { FormattedMessage } from '@kbn/i18n/react'; import { WrapperPage } from '../components/wrapper_page'; -export const NotFoundPage = pure(() => ( +export const NotFoundPage = React.memo(() => ( ( /> )); + NotFoundPage.displayName = 'NotFoundPage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_headers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx similarity index 67% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_headers.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx index d6bfcd80b9956..e90487a3b023c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_headers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx @@ -12,6 +12,48 @@ import { } from '../../../components/timeline/body/helpers'; import * as i18n from './translations'; +import { SubsetTimelineModel, timelineDefaults } from '../../../store/timeline/model'; +import { esFilters } from '../../../../../../../../src/plugins/data/common/es_query'; + +export const signalsOpenFilters: esFilters.Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'open', + }, + }, + query: { + match_phrase: { + 'signal.status': 'open', + }, + }, + }, +]; + +export const signalsClosedFilters: esFilters.Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'closed', + }, + }, + query: { + match_phrase: { + 'signal.status': 'closed', + }, + }, + }, +]; export const signalsHeaders: ColumnHeader[] = [ { @@ -77,3 +119,8 @@ export const signalsHeaders: ColumnHeader[] = [ width: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, ]; + +export const signalsDefaultModel: SubsetTimelineModel = { + ...timelineDefaults, + columns: signalsHeaders, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_model.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_model.tsx deleted file mode 100644 index bb1f806d67c03..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_model.tsx +++ /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 { signalsHeaders } from './default_headers'; -import { SubsetTimelineModel, timelineDefaults } from '../../../store/timeline/model'; - -export const signalsDefaultModel: SubsetTimelineModel = { - ...timelineDefaults, - columns: signalsHeaders, -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx index edc7ed133d10c..ca178db9cd97f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx @@ -12,7 +12,7 @@ import { GlobalTime } from '../../../containers/global_time'; import { StatefulEventsViewer } from '../../../components/events_viewer'; import * as i18n from './translations'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { signalsDefaultModel } from './default_model'; +import { signalsClosedFilters, signalsDefaultModel, signalsOpenFilters } from './default_config'; const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; const FILTER_OPEN = 'open'; @@ -37,7 +37,10 @@ export const SignalsTableFilterGroup = React.memo( setFilterGroup(FILTER_CLOSED)} + onClick={() => { + setFilterGroup(FILTER_CLOSED); + onFilterGroupChanged(FILTER_CLOSED); + }} > {'Closed signals'} @@ -62,6 +65,7 @@ export const SignalsTable = React.memo(() => { {({ to, from, setQuery, deleteQuery, isInitializing }) => ( Math.max(0, windowHeight - globalHeaderSize); -export const HomePage = pure(() => ( +export const HomePage: React.FC = () => ( {({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => ( @@ -115,7 +114,7 @@ export const HomePage = pure(() => ( path={`/:pageName(${SiemPageName.timelines})`} render={() => } /> - + } /> ( @@ -128,7 +127,7 @@ export const HomePage = pure(() => ( )} /> - + } /> )} @@ -141,5 +140,6 @@ export const HomePage = pure(() => ( )} -)); +); + HomePage.displayName = 'HomePage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx index 69ef8579ea8cc..ecd1e4f378cc5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { pure } from 'recompose'; import chrome from 'ui/chrome'; import { documentationLinks } from 'ui/documentation_links'; @@ -15,7 +14,7 @@ import * as i18n from './translations'; const basePath = chrome.getBasePath(); -export const HostsEmptyPage = pure(() => ( +export const HostsEmptyPage = React.memo(() => ( ( +export const NetworkEmptyPage = React.memo(() => ( ( +export const Summary = React.memo(() => (

diff --git a/x-pack/legacy/plugins/siem/public/routes.tsx b/x-pack/legacy/plugins/siem/public/routes.tsx index 9a132eb8d4fac..0e9bcf5dc5bfa 100644 --- a/x-pack/legacy/plugins/siem/public/routes.tsx +++ b/x-pack/legacy/plugins/siem/public/routes.tsx @@ -20,8 +20,8 @@ export const PageRouter: FC = memo(({ history }) => ( - - + } /> + } /> diff --git a/x-pack/legacy/plugins/siem/server/graphql/index.ts b/x-pack/legacy/plugins/siem/server/graphql/index.ts index 901d27295479a..4e28605f6c0b2 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/index.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/index.ts @@ -55,15 +55,3 @@ export const schemas = [ uncommonProcessesSchema, whoAmISchema, ]; - -// The types from graphql-tools/src/mock.ts 'any' based. I add slightly -// stricter types here, but these should go away when graphql-tools using something -// other than "any" in the future for its types. -// https://github.com/apollographql/graphql-tools/blob/master/src/mock.ts#L406 -export interface SiemContext { - req: { - payload: { - operationName: string; - }; - }; -} diff --git a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts index 790b3aaa83095..24589822f0250 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts @@ -9,7 +9,6 @@ import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { IndexFields } from '../../lib/index_fields'; import { SourceStatus } from '../../lib/source_status'; import { QuerySourceResolver } from '../sources/resolvers'; -import { FrameworkFieldsRequest } from '../../lib/index_fields/types'; export type SourceStatusIndicesExistResolver = ChildResolverOf< AppResolverOf, @@ -47,7 +46,7 @@ export const createSourceStatusResolvers = (libs: { ) { return []; } - return libs.fields.getFields(req as FrameworkFieldsRequest, args.defaultIndex); + return libs.fields.getFields(req, args.defaultIndex); }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index 66ec436ea418f..bb0958b32fa19 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -4,16 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; - -import { Logger, EnvironmentMode } from 'src/core/server'; -import { initServer } from './init_server'; -import { compose } from './lib/compose/kibana'; -import { - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, -} from './saved_objects'; +import { PluginInitializerContext } from 'src/core/server'; import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type'; import { isAlertExecutor } from './lib/detection_engine/alerts/types'; @@ -24,74 +15,39 @@ import { readRulesRoute } from './lib/detection_engine/routes/read_rules_route'; import { findRulesRoute } from './lib/detection_engine/routes/find_rules_route'; import { deleteRulesRoute } from './lib/detection_engine/routes/delete_rules_route'; import { updateRulesRoute } from './lib/detection_engine/routes/update_rules_route'; +import { setSignalsStatusRoute } from './lib/detection_engine/routes/signals/open_close_signals_route'; import { ServerFacade } from './types'; import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_index_route'; const APP_ID = 'siem'; -export const initServerWithKibana = ( - kbnServer: ServerFacade, - logger: Logger, - mode: EnvironmentMode -) => { - if (kbnServer.plugins.alerting != null) { - const version = kbnServer.config().get('pkg.version'); +export const initServerWithKibana = (context: PluginInitializerContext, __legacy: ServerFacade) => { + const logger = context.logger.get('plugins', APP_ID); + const version = context.env.packageInfo.version; + + if (__legacy.plugins.alerting != null) { const type = rulesAlertType({ logger, version }); if (isAlertExecutor(type)) { - kbnServer.plugins.alerting.setup.registerType(type); + __legacy.plugins.alerting.setup.registerType(type); } } - kbnServer.injectUiAppVars('siem', async () => kbnServer.getInjectedUiAppVars('kibana')); - - const libs = compose(kbnServer, mode); - initServer(libs); // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules // All REST rule creation, deletion, updating, etc... - createRulesRoute(kbnServer); - readRulesRoute(kbnServer); - updateRulesRoute(kbnServer); - deleteRulesRoute(kbnServer); - findRulesRoute(kbnServer); + createRulesRoute(__legacy); + readRulesRoute(__legacy); + updateRulesRoute(__legacy); + deleteRulesRoute(__legacy); + findRulesRoute(__legacy); + + // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals + // POST /api/detection_engine/signals/status + // Example usage can be found in siem/server/lib/detection_engine/scripts/signals + setSignalsStatusRoute(__legacy); // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces - createIndexRoute(kbnServer); - readIndexRoute(kbnServer); - deleteIndexRoute(kbnServer); - - const xpackMainPlugin = kbnServer.plugins.xpack_main; - xpackMainPlugin.registerFeature({ - id: APP_ID, - name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { - defaultMessage: 'SIEM', - }), - icon: 'securityAnalyticsApp', - navLinkId: 'siem', - app: ['siem', 'kibana'], - catalogue: ['siem'], - privileges: { - all: { - api: ['siem'], - savedObject: { - all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], - read: ['config'], - }, - ui: ['show'], - }, - read: { - api: ['siem'], - savedObject: { - all: [], - read: [ - 'config', - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, - ], - }, - ui: ['show'], - }, - }, - }); + createIndexRoute(__legacy); + readIndexRoute(__legacy); + deleteIndexRoute(__legacy); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts index 6e0c5e98206e4..9d5ac6db7bbb7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EnvironmentMode } from 'src/core/server'; -import { ServerFacade } from '../../types'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Anomalies } from '../anomalies'; import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter'; import { Authentications } from '../authentications'; import { ElasticsearchAuthenticationAdapter } from '../authentications/elasticsearch_adapter'; -import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; import { ElasticsearchEventsAdapter, Events } from '../events'; import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter'; import { ElasticsearchHostsAdapter, Hosts } from '../hosts'; @@ -28,21 +26,20 @@ import { Overview } from '../overview'; import { ElasticsearchOverviewAdapter } from '../overview/elasticsearch_adapter'; import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status'; import { ConfigurationSourcesAdapter, Sources } from '../sources'; -import { AppBackendLibs, AppDomainLibs, Configuration } from '../types'; +import { AppBackendLibs, AppDomainLibs } from '../types'; import { ElasticsearchUncommonProcessesAdapter, UncommonProcesses } from '../uncommon_processes'; import { Note } from '../note/saved_object'; import { PinnedEvent } from '../pinned_event/saved_object'; import { Timeline } from '../timeline/saved_object'; -export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackendLibs { - const configuration = new KibanaConfigurationAdapter(server); - const framework = new KibanaBackendFrameworkAdapter(server, mode); - const sources = new Sources(new ConfigurationSourcesAdapter(configuration)); +export function compose(core: CoreSetup, env: PluginInitializerContext['env']): AppBackendLibs { + const framework = new KibanaBackendFrameworkAdapter(core, env); + const sources = new Sources(new ConfigurationSourcesAdapter()); const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework)); - const timeline = new Timeline({ savedObjects: framework.getSavedObjectsService() }); - const note = new Note({ savedObjects: framework.getSavedObjectsService() }); - const pinnedEvent = new PinnedEvent({ savedObjects: framework.getSavedObjectsService() }); + const timeline = new Timeline(); + const note = new Note(); + const pinnedEvent = new PinnedEvent(); const domainLibs: AppDomainLibs = { anomalies: new Anomalies(new ElasticsearchAnomaliesAdapter(framework)), @@ -60,7 +57,6 @@ export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackend }; const libs: AppBackendLibs = { - configuration, framework, sourceStatus, sources, diff --git a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts deleted file mode 100644 index 153fc032883ff..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/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 { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; - -describe('the KibanaConfigurationAdapter', () => { - test('queries the xpack.siem configuration of the server', async () => { - const mockConfig = { - get: jest.fn(), - }; - - const configurationAdapter = new KibanaConfigurationAdapter({ - config: () => mockConfig, - }); - - await configurationAdapter.get(); - - expect(mockConfig.get).toBeCalledWith('xpack.siem'); - }); - - test('applies the query defaults', async () => { - const configurationAdapter = new KibanaConfigurationAdapter({ - 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/siem/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts deleted file mode 100644 index 83ddbf5cef0e4..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts +++ /dev/null @@ -1,80 +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 { ConfigurationAdapter } from './adapter_types'; - -export class KibanaConfigurationAdapter - implements ConfigurationAdapter { - private readonly server: ServerWithConfig; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - 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.siem') || {}; - const configurationWithDefaults = { - enabled: true, - query: { - partitionSize: 75, - partitionFactor: 1.2, - ...(configuration.query || {}), - }, - sources: {}, - ...configuration, - } as Configuration; - - // we assume this to be the configuration because Kibana would have already validated it - return configurationWithDefaults; - } -} - -interface ServerWithConfig { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config(): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { - return ( - Joi.validate( - maybeServer, - Joi.object({ - config: Joi.func().required(), - }).unknown() - ).error === null - ); -} - -interface KibanaConfiguration { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(key: string): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-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/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md index 755727f9870f6..7c22d6334a2d1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md @@ -60,7 +60,7 @@ which will: - Delete any existing alert tasks you have - Delete any existing signal mapping, policies, and template, you might have previously had. - Add the latest signal index and its mappings using your settings from `kibana.dev.yml` environment variable of `xpack.siem.signalsIndex`. -- Posts the sample rule from `rules/root_or_admin_1.json` +- Posts the sample rule from `./rules/queries/query_with_rule_id.json` - The sample rule checks for root or admin every 5 minutes and reports that as a signal if it is a positive hit Now you can run @@ -154,3 +154,14 @@ logging.events: See these two README.md's pages for more references on the alerting and actions API: https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/alerting/README.md https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/actions + +### Signals API + +To update the status of a signal or group of signals, the following scripts provide an example of how to +go about doing so. +`cd x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` +`./signals/put_signal_doc.sh` will post a sample signal doc into the signals index to play with +`./signals/set_status_with_id.sh closed` will update the status of the sample signal to closed +`./signals/set_status_with_id.sh open` will update the status of the sample signal to open +`./signals/set_status_with_query.sh closed` will update the status of the signals in the result of the query to closed. +`./signals/set_status_with_query.sh open` will update the status of the signals in the result of the query to open. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index bed466dd9b94f..4c113544e6e21 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -22,7 +22,6 @@ export const sampleRuleAlertParams = ( index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], type: 'query', from: 'now-6m', - tags: ['some fake tag'], to: 'now', severity: 'high', query: 'user.name: root or user.name: admin', @@ -31,7 +30,6 @@ export const sampleRuleAlertParams = ( references: ['http://google.com'], riskScore: riskScore ? riskScore : 50, maxSignals: maxSignals ? maxSignals : 10000, - filter: undefined, filters: undefined, savedId: undefined, meta: undefined, @@ -278,7 +276,7 @@ export const sampleRule = (): Partial => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts index 4418bbc52b57d..4cbf3756f58ac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts @@ -13,7 +13,6 @@ export const createRules = async ({ description, enabled, falsePositives, - filter, from, query, language, @@ -38,7 +37,7 @@ export const createRules = async ({ return alertsClient.create({ data: { name, - tags: [], + tags, alertTypeId: SIGNALS_ID, params: { description, @@ -46,7 +45,6 @@ export const createRules = async ({ index, falsePositives, from, - filter, immutable, query, language, @@ -57,7 +55,6 @@ export const createRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts index c55c99fb291c4..e1d10e2efdefb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts @@ -385,26 +385,9 @@ describe('get_filter', () => { }); describe('getFilter', () => { - test('returns a filter if given a type of filter as is', async () => { - const filter = await getFilter({ - type: 'filter', - filter: { something: '1' }, - filters: undefined, - language: undefined, - query: undefined, - savedId: undefined, - services: servicesMock, - index: ['auditbeat-*'], - }); - expect(filter).toEqual({ - something: '1', - }); - }); - test('returns a query if given a type of query', async () => { const filter = await getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -439,7 +422,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: undefined, query: 'host.name: siem', @@ -454,7 +436,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: undefined, @@ -469,7 +450,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -483,7 +463,6 @@ describe('get_filter', () => { test('returns a saved query if given a type of query', async () => { const filter = await getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -507,7 +486,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -522,7 +500,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts index 5d3b47ecebfd5..858f3580f57e8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts @@ -42,7 +42,6 @@ export const getQueryFilter = ( interface GetFilterArgs { type: RuleAlertParams['type']; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; language: string | undefined | null; query: string | undefined | null; @@ -52,7 +51,6 @@ interface GetFilterArgs { } export const getFilter = async ({ - filter, filters, index, language, @@ -95,9 +93,6 @@ export const getFilter = async ({ throw new TypeError('savedId parameter should be defined'); } } - case 'filter': { - return filter; - } } return assertUnreachable(type); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts index 577de2ce0d532..9823d8b3b9bea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts @@ -34,7 +34,6 @@ export const rulesAlertType = ({ description: schema.string(), falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), from: schema.string(), - filter: schema.nullable(schema.object({}, { allowUnknowns: true })), ruleId: schema.string(), immutable: schema.boolean({ defaultValue: false }), index: schema.nullable(schema.arrayOf(schema.string())), @@ -47,7 +46,6 @@ export const rulesAlertType = ({ maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), riskScore: schema.number(), severity: schema.string(), - tags: schema.arrayOf(schema.string(), { defaultValue: [] }), threats: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), to: schema.string(), type: schema.string(), @@ -56,7 +54,6 @@ export const rulesAlertType = ({ }, async executor({ alertId, services, params }) { const { - filter, from, ruleId, index, @@ -72,6 +69,7 @@ export const rulesAlertType = ({ // TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522 const savedObject = await services.savedObjectsClient.get('alert', alertId); const name: string = savedObject.attributes.name; + const tags: string[] = savedObject.attributes.tags; const createdBy: string = savedObject.attributes.createdBy; const updatedBy: string = savedObject.attributes.updatedBy; @@ -87,7 +85,6 @@ export const rulesAlertType = ({ const inputIndex = await getInputIndex(services, version, index); const esFilter = await getFilter({ type, - filter, filters, language, query, @@ -106,18 +103,20 @@ export const rulesAlertType = ({ }); try { - logger.debug(`Starting signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); logger.debug( - `[+] Initial search call of signal rule "id: ${alertId}", "ruleId: ${ruleId}"` + `Starting signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); + logger.debug( + `[+] Initial search call of signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); const noReIndexResult = await services.callCluster('search', noReIndex); if (noReIndexResult.hits.total.value !== 0) { logger.info( `Found ${ noReIndexResult.hits.total.value - } signals from the indexes of "${inputIndex.join( + } signals from the indexes of "[${inputIndex.join( ', ' - )}" using signal rule "id: ${alertId}", "ruleId: ${ruleId}", pushing signals to index ${outputIndex}` + )}]" using signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}", pushing signals to index "${outputIndex}"` ); } @@ -128,24 +127,30 @@ export const rulesAlertType = ({ logger, id: alertId, signalsIndex: outputIndex, + filter: esFilter, name, createdBy, updatedBy, interval, enabled, pageSize: searchAfterSize, + tags, }); if (bulkIndexResult) { - logger.debug(`Finished signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.debug( + `Finished signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } else { - logger.error(`Error processing signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.error( + `Error processing signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } } catch (err) { // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule "id: ${alertId}", "ruleId: ${ruleId}", ${err.message}` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); } }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index 843ba4b116779..c9d265ebffacd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -35,7 +35,6 @@ export interface RuleAlertParams { description: string; enabled: boolean; falsePositives: string[]; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; from: string; immutable: boolean; @@ -55,7 +54,7 @@ export interface RuleAlertParams { tags: string[]; to: string; threats: ThreatParams[] | undefined | null; - type: 'filter' | 'query' | 'saved_query'; + type: 'query' | 'saved_query'; } export type RuleAlertParamsRest = Omit< @@ -70,6 +69,16 @@ export type RuleAlertParamsRest = Omit< output_index: RuleAlertParams['outputIndex']; }; +export interface SignalsParams { + signalIds: string[] | undefined | null; + query: object | undefined | null; + status: 'open' | 'closed'; +} + +export type SignalsRestParams = Omit & { + signal_ids: SignalsParams['signalIds']; +}; + export type OutputRuleAlertRest = RuleAlertParamsRest & { id: string; created_by: string | undefined | null; @@ -139,7 +148,7 @@ export interface ReadRuleByRuleId { ruleId: string; } -export type RuleTypeParams = Omit; +export type RuleTypeParams = Omit; export type RuleAlertType = Alert & { id: string; @@ -150,6 +159,10 @@ export interface RulesRequest extends RequestFacade { payload: RuleAlertParamsRest; } +export interface SignalsRequest extends RequestFacade { + payload: SignalsRestParams; +} + export interface UpdateRulesRequest extends RequestFacade { payload: UpdateRuleAlertParamsRest; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts index d6b828642433d..2eaa05ae2fa6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts @@ -53,7 +53,6 @@ export const updateRules = async ({ savedId, meta, filters, - filter, from, immutable, id, @@ -88,7 +87,6 @@ export const updateRules = async ({ { description, falsePositives, - filter, from, immutable, query, @@ -101,7 +99,6 @@ export const updateRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, @@ -114,11 +111,10 @@ export const updateRules = async ({ } else if (!rule.enabled && enabled) { await alertsClient.enable({ id: rule.id }); } - return alertsClient.update({ id: rule.id, data: { - tags: [], + tags: tags != null ? tags : [], name: calculateName({ updatedName: name, originalName: rule.name }), interval: calculateInterval(interval, rule.interval), actions, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index fc50e54e06e4e..41052ab4bbb15 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -68,6 +68,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -102,7 +103,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -131,6 +132,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -174,7 +176,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -202,6 +204,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -244,7 +247,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -270,6 +273,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -307,7 +311,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -448,6 +452,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -475,6 +480,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -494,6 +500,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -513,6 +520,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(successfulsingleBulkCreate).toEqual(true); @@ -530,6 +538,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Attempted to search after with empty sort id'); }); @@ -543,6 +552,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }); expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); }); @@ -559,6 +569,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Fake Error'); }); @@ -579,6 +590,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); @@ -629,6 +642,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); @@ -650,6 +665,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -678,6 +695,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -706,6 +725,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -736,6 +757,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -766,6 +789,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -798,6 +823,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(false); }); @@ -873,7 +900,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -926,7 +953,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -957,6 +984,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -977,7 +1005,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1007,6 +1035,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1027,7 +1056,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1046,6 +1075,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1066,7 +1096,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index 9dedda6d79839..1787aa3a3081b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -26,6 +26,7 @@ interface BuildRuleParams { createdBy: string; updatedBy: string; interval: string; + tags: string[]; } export const buildRule = ({ @@ -36,6 +37,7 @@ export const buildRule = ({ createdBy, updatedBy, interval, + tags, }: BuildRuleParams): Partial => { return pickBy((value: unknown) => value != null, { id, @@ -47,7 +49,6 @@ export const buildRule = ({ risk_score: ruleParams.riskScore, output_index: ruleParams.outputIndex, description: ruleParams.description, - filter: ruleParams.filter, from: ruleParams.from, immutable: ruleParams.immutable, index: ruleParams.index, @@ -57,7 +58,7 @@ export const buildRule = ({ query: ruleParams.query, references: ruleParams.references, severity: ruleParams.severity, - tags: ruleParams.tags, + tags, type: ruleParams.type, to: ruleParams.to, enabled, @@ -95,6 +96,7 @@ interface BuildBulkBodyParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const buildEventTypeSignal = (doc: SignalSourceHit): object => { @@ -115,6 +117,7 @@ export const buildBulkBody = ({ updatedBy, interval, enabled, + tags, }: BuildBulkBodyParams): SignalHit => { const rule = buildRule({ ruleParams, @@ -124,6 +127,7 @@ export const buildBulkBody = ({ createdBy, updatedBy, interval, + tags, }); const signal = buildSignal(doc, rule); const event = buildEventTypeSignal(doc); @@ -148,6 +152,7 @@ interface SingleBulkCreateParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const generateId = ( @@ -173,6 +178,7 @@ export const singleBulkCreate = async ({ updatedBy, interval, enabled, + tags, }: SingleBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -198,7 +204,7 @@ export const singleBulkCreate = async ({ ), }, }, - buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled }), + buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), ]); const time1 = performance.now(); const firstResult: BulkResponse = await services.callCluster('bulk', { @@ -207,7 +213,9 @@ export const singleBulkCreate = async ({ body: bulkBody, }); const time2 = performance.now(); - logger.debug(`individual bulk process time took: ${time2 - time1} milliseconds`); + logger.debug( + `individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds` + ); logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`); if (firstResult.errors) { // go through the response status errors and see what @@ -241,6 +249,7 @@ interface SingleSearchAfterParams { services: AlertServices; logger: Logger; pageSize: number; + filter: unknown; } // utilize search_after for paging results into bulk. @@ -248,6 +257,7 @@ export const singleSearchAfter = async ({ searchAfterSortId, ruleParams, services, + filter, logger, pageSize, }: SingleSearchAfterParams): Promise => { @@ -259,7 +269,7 @@ export const singleSearchAfter = async ({ index: ruleParams.index, from: ruleParams.from, to: ruleParams.to, - filter: ruleParams.filter, + filter, size: pageSize, searchAfterSortId, }); @@ -287,6 +297,8 @@ interface SearchAfterAndBulkCreateParams { interval: string; enabled: boolean; pageSize: number; + filter: unknown; + tags: string[]; } // search_after through documents and re-index using bulk endpoint. @@ -297,12 +309,14 @@ export const searchAfterAndBulkCreate = async ({ logger, id, signalsIndex, + filter, name, createdBy, updatedBy, interval, enabled, pageSize, + tags, }: SearchAfterAndBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -321,6 +335,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); const totalHits = typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; @@ -353,6 +368,7 @@ export const searchAfterAndBulkCreate = async ({ ruleParams, services, logger, + filter, pageSize, // maximum number of docs to receive per search result. }); if (searchAfterResult.hits.hits.length === 0) { @@ -379,6 +395,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); logger.debug('finished next bulk index'); } catch (exc) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts index 3c19ff6f1bb65..8b7f6c765e710 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts @@ -6,9 +6,10 @@ import Hapi from 'hapi'; import { KibanaConfig } from 'src/legacy/server/kbn_server'; +import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; + import { alertsClientMock } from '../../../../../../alerting/server/alerts_client.mock'; import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; -import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; const defaultConfig = { 'kibana.index': '.kibana', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index e64153e81fe4a..cd8b716221b9b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,8 +6,11 @@ import { ServerInjectOptions } from 'hapi'; import { ActionResult } from '../../../../../../actions/server/types'; -import { RuleAlertParamsRest, RuleAlertType } from '../../alerts/types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { RuleAlertParamsRest, RuleAlertType, SignalsRestParams } from '../../alerts/types'; +import { + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_SIGNALS_STATUS_URL, +} from '../../../../../common/constants'; // The Omit of filter is because of a Hapi Server Typing issue that I am unclear // where it comes from. I would hope to remove the "filter" as an omit at some point @@ -35,18 +38,18 @@ export const typicalPayload = (): Partial> = ], }); -export const typicalFilterPayload = (): Partial => ({ - rule_id: 'rule-1', - description: 'Detecting root and admin users', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - name: 'Detect Root/Admin Users', - risk_score: 50, - type: 'filter', - from: 'now-6m', - to: 'now', - severity: 'high', - filter: {}, +export const typicalSetStatusSignalByIdsPayload = (): Partial => ({ + signal_ids: ['somefakeid1', 'somefakeid2'], + status: 'closed', +}); + +export const typicalSetStatusSignalByQueryPayload = (): Partial => ({ + query: { range: { '@timestamp': { gte: 'now-2M', lte: 'now/M' } } }, + status: 'closed', +}); + +export const setStatusSignalMissingIdsAndQueryPayload = (): Partial => ({ + status: 'closed', }); export const getUpdateRequest = (): ServerInjectOptions => ({ @@ -113,6 +116,22 @@ export const getCreateRequest = (): ServerInjectOptions => ({ }, }); +export const getSetSignalStatusByIdsRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: { + ...typicalSetStatusSignalByIdsPayload(), + }, +}); + +export const getSetSignalStatusByQueryRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: { + ...typicalSetStatusSignalByQueryPayload(), + }, +}); + export const createActionResult = (): ActionResult => ({ id: 'result-1', actionTypeId: 'action-id-1', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts index 4aa57f005445b..b271af2db1e7d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts @@ -103,27 +103,6 @@ describe('create_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResult()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.create.mockResolvedValue(createActionResult()); - alertsClient.create.mockResolvedValue(getResult()); - // Cannot type request with a ServerInjectOptions as the type system complains - // about the property filter involving Hapi types, so I left it off for now - const { language, query, type, ...noType } = typicalPayload(); - const request = { - method: 'POST', - url: DETECTION_ENGINE_RULES_URL, - payload: { - ...noType, - type: 'filter', - filter: {}, - }, - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResult()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index 31068dac5d23a..a137d54250189 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -35,7 +35,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -68,7 +67,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = try { const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, finalIndex); if (!indexExists) { return new Boom( @@ -81,7 +80,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = if (ruleId != null) { const rule = await readRules({ alertsClient, ruleId }); if (rule != null) { - return new Boom(`rule_id ${ruleId} already exists`, { statusCode: 409 }); + return new Boom(`rule_id: "${ruleId}" already exists`, { statusCode: 409 }); } } const createdRule = await createRules({ @@ -90,7 +89,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, falsePositives, - filter, from, immutable, query, @@ -120,6 +118,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = }; }; -export const createRulesRoute = (server: ServerFacade) => { +export const createRulesRoute = (server: ServerFacade): void => { server.route(createCreateRulesRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 14a061fd4ccb7..94c42664c281d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -34,10 +34,10 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { - return new Boom(`index ${index} already exists`, { statusCode: 409 }); + return new Boom(`index: "${index}" already exists`, { statusCode: 409 }); } else { const policyExists = await getPolicyExists(callWithRequest, index); if (!policyExists) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts index 89f21bbada939..82fe0f55215fb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -42,10 +42,10 @@ export const createDeleteIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (!indexExists) { - return new Boom(`index ${index} does not exist`, { statusCode: 404 }); + return new Boom(`index: "${index}" does not exist`, { statusCode: 404 }); } else { await deleteAllIndex(callWithRequest, `${index}-*`); const policyExists = await getPolicyExists(callWithRequest, index); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts index 87b6ffb985c37..a8c4b7407c448 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts @@ -27,7 +27,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => async handler(request: RequestFacade, headers) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { // head request is used for if you want to get if the index exists @@ -42,7 +42,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => if (request.method.toLowerCase() === 'head') { return headers.response().code(404); } else { - return new Boom('An index for this space does not exist', { statusCode: 404 }); + return new Boom('index for this space does not exist', { statusCode: 404 }); } } } catch (err) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts index 690d9b11b1455..f5147bc5a8f8b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -4,12 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createRulesSchema, updateRulesSchema, findRulesSchema, queryRulesSchema } from './schemas'; +import { + createRulesSchema, + updateRulesSchema, + findRulesSchema, + queryRulesSchema, + setSignalsStatusSchema, +} from './schemas'; import { RuleAlertParamsRest, FindParamsRest, UpdateRuleAlertParamsRest, ThreatParams, + SignalsRestParams, } from '../alerts/types'; describe('schemas', () => { @@ -134,7 +141,7 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does not validate', () => { + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { expect( createRulesSchema.validate>({ rule_id: 'rule-1', @@ -149,7 +156,7 @@ describe('schemas', () => { index: ['index-1'], interval: '5m', }).error - ).toBeTruthy(); + ).toBeFalsy(); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -220,8 +227,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', risk_score: 50, }).error ).toBeFalsy(); @@ -240,8 +246,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -280,8 +285,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', threats: [ { framework: 'someFramework', @@ -303,84 +307,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('If filter type is set then filter is required', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then query is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as valid', () => { expect( createRulesSchema.validate>({ @@ -502,26 +428,6 @@ describe('schemas', () => { ).toEqual(100); }); - test('filter and filters cannot exist together', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is required when type is saved_query and will not validate without out', () => { expect( createRulesSchema.validate>({ @@ -601,26 +507,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( createRulesSchema.validate>({ @@ -1150,30 +1036,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('You can have an empty query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: '', - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - test('You can omit the query string when filters are present', () => { expect( createRulesSchema.validate & { meta: string }>>({ @@ -1196,29 +1058,6 @@ describe('schemas', () => { }).error ).toBeFalsy(); }); - - test('query string defaults to empty string when present with filters', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).value.query - ).toEqual(''); - }); }); describe('update rules schema', () => { @@ -1549,8 +1388,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -1566,82 +1404,11 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then filter is still not required', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', + type: 'query', }).error ).toBeFalsy(); }); - test('If filter type is set then query is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as a valid value to update with', () => { expect( updateRulesSchema.validate>({ @@ -1751,24 +1518,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('filter and filters cannot exist together', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is not required when type is saved_query and will validate without it', () => { expect( updateRulesSchema.validate>({ @@ -1820,24 +1569,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( updateRulesSchema.validate>({ @@ -2006,186 +1737,187 @@ describe('schemas', () => { }).value.threats ).toBe(undefined); }); - }); - test('threats is not defaulted to undefined on update with empty array', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).value.threats - ).toMatchObject([]); - }); - test('threats is valid when updated with all sub-objects', () => { - const expected: ThreatParams[] = [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', + + test('threats is not defaulted to undefined on update with empty array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).value.threats + ).toMatchObject([]); + }); + test('threats is valid when updated with all sub-objects', () => { + const expected: ThreatParams[] = [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', }, - ], - }, - ]; - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', + ], + }, + ]; + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', }, - ], - }, - ], - }).value.threats - ).toMatchObject(expected); - }); - test('threats is invalid when updated with missing property framework', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', + ], + }).value.threats + ).toMatchObject(expected); + }); + test('threats is invalid when updated with missing property framework', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing tactic sub-object', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing tactic sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing techniques', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { id: 'techniqueId', name: 'techniqueName', reference: 'techniqueRef', }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing techniques', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', }, - }, - ], - }).error - ).toBeTruthy(); + ], + }).error + ).toBeTruthy(); + }); }); describe('find rules schema', () => { @@ -2340,4 +2072,62 @@ describe('schemas', () => { ).toBeFalsy(); }); }); + + describe('set signal status schema', () => { + test('signal_ids and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('query and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('signal_ids and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + }).error + ).toBeTruthy(); + }); + + test('query and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + }).error + ).toBeTruthy(); + }); + + test('status is present but query or signal_ids is missing is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + status: 'closed', + }).error + ).toBeTruthy(); + }); + + test('signal_ids is present but status has wrong value', () => { + expect( + setSignalsStatusSchema.validate< + Partial< + Omit & { + status: string; + } + > + >({ + status: 'fakeVal', + }).error + ).toBeTruthy(); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index 59d52c113cbf2..6ed6fdd2577d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -11,7 +11,6 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; const description = Joi.string(); const enabled = Joi.boolean(); const false_positives = Joi.array().items(Joi.string()); -const filter = Joi.object(); const filters = Joi.array(); const from = Joi.string(); const immutable = Joi.boolean(); @@ -32,8 +31,9 @@ const risk_score = Joi.number() .greater(-1) .less(101); const severity = Joi.string(); +const status = Joi.string().valid('open', 'closed'); const to = Joi.string(); -const type = Joi.string().valid('filter', 'query', 'saved_query'); +const type = Joi.string().valid('query', 'saved_query'); const queryFilter = Joi.string(); const references = Joi.array() .items(Joi.string()) @@ -44,6 +44,8 @@ const per_page = Joi.number() const page = Joi.number() .min(1) .default(1); +const signal_ids = Joi.array().items(Joi.string()); +const signal_status_query = Joi.object(); const sort_field = Joi.string(); const sort_order = Joi.string().valid('asc', 'desc'); const tags = Joi.array().items(Joi.string()); @@ -82,46 +84,14 @@ export const createRulesSchema = Joi.object({ description: description.required(), enabled: enabled.default(true), false_positives: false_positives.default([]), - filter: filter.when('type', { is: 'filter', then: Joi.required(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from: from.required(), rule_id, immutable: immutable.default(false), index, interval: interval.default('5m'), - query: Joi.when('type', { - is: 'query', - then: Joi.when('filters', { - is: Joi.exist(), - then: query - .optional() - .allow('') - .default(''), - otherwise: Joi.required(), - }), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.required(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow('').default(''), + language: language.default('kuery'), output_index, saved_id: saved_id.when('type', { is: 'saved_query', @@ -144,46 +114,17 @@ export const updateRulesSchema = Joi.object({ description, enabled, false_positives, - filter: filter.when('type', { is: 'filter', then: Joi.optional(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from, rule_id, id, immutable, index, interval, - query: Joi.when('type', { - is: 'query', - then: query.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow(''), + language, output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.optional(), - otherwise: Joi.forbidden(), - }), + saved_id, meta, risk_score, max_signals, @@ -213,3 +154,9 @@ export const findRulesSchema = Joi.object({ }), sort_order, }); + +export const setSignalsStatusSchema = Joi.object({ + signal_ids, + query: signal_status_query, + status: status.required(), +}).xor('signal_ids', 'query'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts new file mode 100644 index 0000000000000..3cb64586d6cb5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -0,0 +1,114 @@ +/* + * 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 { createMockServer } from '../__mocks__/_mock_server'; +import { setSignalsStatusRoute } from './open_close_signals_route'; +import * as myUtils from '../utils'; +import { ServerInjectOptions } from 'hapi'; +import { + getSetSignalStatusByIdsRequest, + getSetSignalStatusByQueryRequest, + typicalSetStatusSignalByIdsPayload, + typicalSetStatusSignalByQueryPayload, + setStatusSignalMissingIdsAndQueryPayload, +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; + +describe('set signal status', () => { + let { server, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(myUtils, 'getIndex').mockReturnValue('fakeindex'); + ({ server, elasticsearch } = createMockServer()); + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn(() => true), + })); + setSignalsStatusRoute(server); + }); + + describe('status on signal', () => { + test('returns 200 when setting a status on a signal by ids', async () => { + const { statusCode } = await server.inject(getSetSignalStatusByIdsRequest()); + expect(statusCode).toBe(200); + expect(myUtils.getIndex).toHaveReturnedWith('fakeindex'); + }); + + test('returns 200 when setting a status on a signal by query', async () => { + const { statusCode } = await server.inject(getSetSignalStatusByQueryRequest()); + expect(statusCode).toBe(200); + }); + }); + + describe('validation', () => { + test('returns 200 if signal_ids and status are present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: typicalSetStatusSignalByIdsPayload(), + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 200 if query and status are present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: typicalSetStatusSignalByQueryPayload(), + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 400 if signal_ids and query are not present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: setStatusSignalMissingIdsAndQueryPayload(), + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + + test('returns 400 if signal_ids are present but status is not', async () => { + const { status, ...justIds } = typicalSetStatusSignalByIdsPayload(); + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: justIds, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + + test('returns 400 if query is present but status is not', async () => { + const { status, ...justTheQuery } = typicalSetStatusSignalByQueryPayload(); + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: justTheQuery, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + + test('returns 400 if query and signal_ids are present but status is not', async () => { + const allTogether = { + ...typicalSetStatusSignalByIdsPayload(), + ...typicalSetStatusSignalByQueryPayload(), + }; + const { status, ...queryAndSignalIdsNoStatus } = allTogether; + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_SIGNALS_STATUS_URL, + payload: queryAndSignalIdsNoStatus, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts new file mode 100644 index 0000000000000..99af43ce51a12 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -0,0 +1,59 @@ +/* + * 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 Hapi from 'hapi'; +import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; +import { SignalsRequest } from '../../alerts/types'; +import { setSignalsStatusSchema } from '../schemas'; +import { ServerFacade } from '../../../../types'; +import { transformError, getIndex } from '../utils'; + +export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'POST', + path: DETECTION_ENGINE_SIGNALS_STATUS_URL, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: setSignalsStatusSchema, + }, + }, + async handler(request: SignalsRequest, headers) { + const { signal_ids: signalIds, query, status } = request.payload; + const index = getIndex(request, server); + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + let queryObject; + if (signalIds) { + queryObject = { ids: { values: signalIds } }; + } + if (query) { + queryObject = query; + } + try { + return callWithRequest(request, 'updateByQuery', { + index, + body: { + script: { + source: `ctx._source.signal.status = '${status}'`, + lang: 'painless', + }, + query: queryObject, + }, + }); + } catch (exc) { + // error while getting or updating signal with id: id in signal index .siem-signals + return transformError(exc); + } + }, + }; +}; + +export const setSignalsStatusRoute = (server: ServerFacade) => { + server.route(setSignalsStatusRouteDef(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts index d03d68417dd5d..dfa1275a6b26b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts @@ -20,7 +20,6 @@ import { getUpdateRequest, typicalPayload, getFindResultWithSingleHit, - typicalFilterPayload, } from './__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; @@ -119,20 +118,6 @@ describe('update_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.update.mockResolvedValue(updateActionResult()); - alertsClient.update.mockResolvedValue(getResult()); - const request: ServerInjectOptions = { - method: 'PUT', - url: DETECTION_ENGINE_RULES_URL, - payload: typicalFilterPayload(), - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts index 156756698435f..943c41fd6dea6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts @@ -30,7 +30,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -68,7 +67,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, falsePositives, - filter, from, immutable, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index d20b4d213e9cc..4663ea357f259 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -341,22 +341,22 @@ describe('utils', () => { describe('getIdError', () => { test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { const boom = getIdError({ id: '123', ruleId: undefined }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about id not being found if only id is defined and ruleId is null', () => { const boom = getIdError({ id: '123', ruleId: null }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { const boom = getIdError({ id: undefined, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { const boom = getIdError({ id: null, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about both being not defined when both are undefined', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 4c5a5a6af93de..6df4174e628b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -18,9 +18,9 @@ export const getIdError = ({ ruleId: string | undefined | null; }) => { if (id != null) { - return new Boom(`id of ${id} not found`, { statusCode: 404 }); + return new Boom(`id: "${id}" not found`, { statusCode: 404 }); } else if (ruleId != null) { - return new Boom(`rule_id of ${ruleId} not found`, { statusCode: 404 }); + return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); } else { return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); } @@ -34,7 +34,6 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial { - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); +export const callWithRequestFactory = (request: RequestFacade, server: ServerFacade) => { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); return (endpoint: string, params: T, options?: U) => { return callWithRequest(request, endpoint, params, options); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh index 34b6208947c57..52d49979a072b 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh @@ -13,6 +13,7 @@ FILTER=${1:-'alert.attributes.enabled:%20true'} # Example: ./find_rule_by_filter.sh "alert.attributes.enabled:%20true" # Example: ./find_rule_by_filter.sh "alert.attributes.name:%20Detect*" +# Example: ./find_rule_by_filter.sh "alert.attributes.tags:tag_1" # The %20 is just an encoded space that is typical of URL's. # Table of them for testing if needed: https://www.w3schools.com/tags/ref_urlencode.asp curl -s -k \ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh index 591cf7625e2e3..432045634ba7b 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh @@ -10,11 +10,11 @@ set -e ./check_env_variables.sh # Uses a default if no argument is specified -RULES=(${@:-./rules/root_or_admin_1.json}) +RULES=(${@:-./rules/queries/query_with_rule_id.json}) # Example: ./post_rule.sh -# Example: ./post_rule.sh ./rules/root_or_admin_1.json -# Example glob: ./post_rule.sh ./rules/* +# Example: ./post_rule.sh ./rules/queries/query_with_rule_id.json +# Example glob: ./post_rule.sh ./rules/queries/* for RULE in "${RULES[@]}" do { [ -e "$RULE" ] || continue diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md new file mode 100644 index 0000000000000..04db6c683d726 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md @@ -0,0 +1,21 @@ +These are example POST rules that only have queries and and no filters in them. + +Every single json file should have the field: + +```sh +"type": "query" +``` + +set which is what designates it as a type of query. + +To post all of them to see in the UI, with the scripts folder as your current working directory. + +```sh +./post_rule.sh ./rules/queries/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/queries/.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json new file mode 100644 index 0000000000000..38b3ed9f74696 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is disabled", + "description": "Example query which will is disabled and will not run after being posted", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json new file mode 100644 index 0000000000000..681d66e16d0ba --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is immutable", + "description": "Example query which is immutable", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "immutable": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json new file mode 100644 index 0000000000000..ed8849831a479 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json @@ -0,0 +1,11 @@ +{ + "name": "Query with the language set to lucene", + "description": "Query with the language set to lucene", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "language": "lucene" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json similarity index 74% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json index 9ca4c32631334..721a172ce55d7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json @@ -1,17 +1,12 @@ { - "rule_id": "rule-1", - "description": "Detecting root and admin users", - "index": ["auditbeat-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], - "interval": "5s", - "name": "Detect Root/Admin Users", - "severity": "high", + "name": "Query which has Mitre Attack Data", + "description": "Example query which has Mitre Attack Data as threats", "risk_score": 1, + "severity": "high", "type": "query", - "from": "now-6s", + "from": "now-6m", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], "threats": [ { "framework": "MITRE ATT&CK", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json new file mode 100644 index 0000000000000..adb96ba398f5e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Query with all possible fields filled out", + "description": "Kitchen Sink (everything) query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json similarity index 62% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json index eb7f2ae03b64b..c754ab73ea21e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json @@ -1,15 +1,13 @@ { - "rule_id": "rule-5", - "risk_score": 5, - "description": "Detecting root and admin users over 24 hours on windows", - "interval": "5m", - "name": "Detect Root/Admin Users", + "name": "Query with two filters", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, "severity": "high", "type": "query", "from": "now-24h", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json new file mode 100644 index 0000000000000..f9f5bf854e45c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json @@ -0,0 +1,17 @@ +{ + "name": "Query which has extra meta data", + "description": "Query which has extra meta data", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "meta": { + "whatever-you-want": { + "store-stateful-stuff-for-ui": { + "or-anything-else": true + } + } + } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json new file mode 100644 index 0000000000000..e4da196007527 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Query with a rule id", + "description": "Query with a rule_id that acts like an external id", + "rule_id": "query-rule-id", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json new file mode 100644 index 0000000000000..954f5942180d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json @@ -0,0 +1,12 @@ +{ + "name": "Query with a set of tags", + "description": "Query with a set a tags", + "rule_id": "tags-query", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "tags": ["tag_1", "tag_2"] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json new file mode 100644 index 0000000000000..61e68f886ffe7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json @@ -0,0 +1,18 @@ +{ + "name": "Simplest Filter", + "description": "Simplest filter with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json new file mode 100644 index 0000000000000..e812b031a28fd --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Query", + "description": "Simplest query with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json deleted file mode 100644 index b00a5929d9ef1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json deleted file mode 100644 index 657439104e306..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json deleted file mode 100644 index 137cf7eedbccf..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-2", - "risk_score": 2, - "description": "Detecting root and admin users over a long period of time", - "interval": "24h", - "name": "Detect Root/Admin Users over a long period of time", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json deleted file mode 100644 index b9160c95621ee..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-3", - "risk_score": 3, - "description": "Detecting root and admin users as an empty set", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-16y", - "to": "now-15y", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json deleted file mode 100644 index 364e7f00c9571..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-4", - "risk_score": 4, - "description": "Detecting root and admin users with lucene", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "lucene", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json deleted file mode 100644 index de24263c6af5c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rule_id": "rule-8", - "risk_score": 8, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json deleted file mode 100644 index 9bf2b1abf5f90..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "rule_id": "rule-9", - "risk_score": 9, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "tags": ["tag_1", "tag_2"], - "false_positives": ["false_1", "false_2"], - "immutable": true, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json deleted file mode 100644 index 2381e9e259c07..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "must": [], - "filter": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "host.name": "siem-windows" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "match_phrase": { - "winlog.event_id": { - "query": "100" - } - } - }, - { - "match_phrase": { - "agent.hostname": { - "query": "siem-windows" - } - } - } - ], - "should": [], - "must_not": [] - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json deleted file mode 100644 index ee8fe1fc93fb3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "should": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "root" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "admin" - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json deleted file mode 100644 index ed8f2e5745bea..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "rule_id": "rule-meta-data", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], - "meta": { - "anything_i_want": { - "total_meta_for_ui_needs": true - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json deleted file mode 100644 index 721644acd989d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-1", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json deleted file mode 100644 index b733b6bb8c592..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "saved-query-2", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-2", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json deleted file mode 100644 index df1b37f19bf29..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-3", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-3" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json deleted file mode 100644 index 09ddfb1c34a92..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 98, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json deleted file mode 100644 index 8a3c765519ef3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 78, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "false_positives": ["false_update_1", "false_update_2"], - "from": "now-6m", - "immutable": true, - "tags": ["some other tag for you"], - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md new file mode 100644 index 0000000000000..bdd4f33c35c78 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md @@ -0,0 +1,32 @@ +These are example POST rules that have the ability to use saved query id's and optionally +queries and filters. If you only use a saved query id, then detection engine relies on that +saved query id existing or it will throw errors if the user deletes the saved query id. If you +add a saved query id along side with a filter and/or query then it will try to use the saved query +id first and if that fails it will fall back on the provided filter and/or query. + +Every single json file should have the field: + +```sh +"type": "saved_query" +``` + +set which is what designates it as a type of saved_query + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/saved_queries/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/saved_queries/.json +``` + +If the saved_id does not exist and you do not provide a query and/or filter then expect to see this +in your kibana console logging: + +```sh +server log [11:48:33.331] [error][task_manager] Task alerting:siem.signals "fedc2390-1858-11ea-9184-15f04d7099dc" failed: Error: Saved object [query/test-saved-id] not found +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json new file mode 100644 index 0000000000000..0e0be24c00207 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query that uses a rule-id", + "rule_id": "simplest-saved-query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json new file mode 100644 index 0000000000000..c4aaf4aae93c2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -0,0 +1,81 @@ +{ + "name": "Saved Query with all possible fields filled out", + "description": "Kitchen Sink (everything) saved query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "saved-query-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "saved_query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json new file mode 100644 index 0000000000000..55f95e9644b8b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json @@ -0,0 +1,19 @@ +{ + "name": "Simplest saved id with filters", + "description": "Simplest saved_id with a filter for a fallback if the saved_id is not found", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json new file mode 100644 index 0000000000000..ee37c4cb784d1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query", + "description": "Simplest saved query with a fallback of a query", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json new file mode 100644 index 0000000000000..19801e7a98ac2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json @@ -0,0 +1,26 @@ +{ + "name": "Query with filter", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, + "severity": "high", + "type": "query", + "from": "now-24h", + "to": "now", + "query": "user.name: root or user.name: admin", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json new file mode 100644 index 0000000000000..a3dbf0f1b09af --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Saved Query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md new file mode 100644 index 0000000000000..8b6508c64dc5c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md @@ -0,0 +1,18 @@ +These are example POST rules that are more either Kibana UI test cases, mis-use test cases, +any generic e2e based test cases for testing different scenarios. Normally you would not +use these type of rule based messages when writing pure REST API calls. These messages are +more of what you would see "behind the scenes" when you are using Kibana UI which can +create rules with additional "meta" data or other implementation details that aren't really +a concern for a regular REST API user. + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/test_cases/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/test_cases/.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json similarity index 80% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json index 81ec19a4fd0ef..ced59c3ef2c7b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-7", + "name": "Query Filter With UI Meta Data With Lucene as a language", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-lucene", "risk_score": 7, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json similarity index 82% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json index 94f30bc9f92df..21fe7f7e2557a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-6", + "name": "Query Filter With UI Meta Data", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-kuery", "risk_score": 6, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json new file mode 100644 index 0000000000000..607dcd963780c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json @@ -0,0 +1,36 @@ +{ + "type": "saved_query", + "index": ["auditbeat-*", "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], + "language": "kuery", + "filters": [ + { + "meta": { + "alias": null, + "negate": false, + "disabled": false, + "type": "phrase", + "key": "source.ip", + "params": { "query": "127.0.0.1" } + }, + "query": { + "match": { "source.ip": { "query": "127.0.0.1", "type": "phrase" } } + }, + "$state": { "store": "appState" } + } + ], + "output_index": ".siem-signals-default", + "query": "", + "saved_id": "User's IP", + "false_positives": [], + "references": [], + "risk_score": 21, + "name": "Signal Maker 5000", + "description": "It's the Garrett", + "severity": "low", + "tags": ["Spong"], + "interval": "5m", + "from": "now-300s", + "enabled": true, + "to": "now", + "meta": { "from": "now-300s" } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md new file mode 100644 index 0000000000000..97a5d31bb0133 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md @@ -0,0 +1,25 @@ +These are example PUT rules to see how to update various parts of the rules. +You either have to use the id, or you have to use the rule_id in order to update +the rules. rule_id acts as an external_id where you can update rules across different +Kibana systems where id acts as a normal server generated id which is not normally shared +across different Kibana systems. + +The only thing you cannot update is the `rule_id` or regular `id` of the system. If `rule_id` +is incorrect then you have to delete the rule completely and re-initialize it with the +correct `rule_id` + +First add all the examples from queries like so: + +```sh +./post_rule.sh ./rules/queries/*.json +``` + +Then to selectively update a rule add the file of your choosing to update: + +```sh +./update_rule.sh ./rules/updates/.json +``` + +Take note that the ones with "id" must be changed to a GUID that only you know about through +a `./find_rules.sh`. For example to grab a GUID id off of the first found record that exists +you can do: `./find_rules.sh | jq '.data[0].id'` and then replace the id in `updates/simplest_update_risk_score_by_id.json` with that particular id to watch it happen. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json new file mode 100644 index 0000000000000..a94558143882b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json new file mode 100644 index 0000000000000..bfe7c7f546fc3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json new file mode 100644 index 0000000000000..00966ddba7c7a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json @@ -0,0 +1,4 @@ +{ + "id": "ade31ba8-dc49-4c18-b7f4-370b35df5f57", + "risk_score": 38 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json new file mode 100644 index 0000000000000..ad3c78183297d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "risk_score": 98 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json new file mode 100644 index 0000000000000..56c9f151dc712 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "name": "Changes only the name to this new value" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json new file mode 100644 index 0000000000000..285e9c94a76d9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Updates a query with all possible fields that can be updated", + "description": "Kitchen Sink (everything) query that has all possible fields filled out.", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json new file mode 100644 index 0000000000000..be833105792c6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json @@ -0,0 +1,4 @@ +{ + "rule_id": "tags-query", + "tags": ["tag_3"] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json deleted file mode 100644 index a43398bd6876a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-longmont", - "risk_score": 5, - "description": "Detect Longmont activity", - "interval": "24h", - "name": "Detect Longmont activity", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/put_signal_doc.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/put_signal_doc.sh new file mode 100755 index 0000000000000..dd107ae94ebbb --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/put_signal_doc.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +SIGNALS_INDEX=$(./get_signal_index.sh | jq ".name" -j) + +# Example: ./put_signal_doc.sh +curl -s -k \ + -H "Content-Type: application/json" \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -d @./signals/sample_signal.json \ + -X PUT ${ELASTICSEARCH_URL}/$SIGNALS_INDEX/_doc/45562a28e0dFakeSignalId \ + | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/sample_signal.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/sample_signal.json new file mode 100644 index 0000000000000..8cba054d4d205 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/sample_signal.json @@ -0,0 +1,198 @@ +{ + "@timestamp": "2019-11-27T00:11:45.677Z", + "host": { + "architecture": "x86_64", + "os": { + "kernel": "5.0.0-1015-gcp", + "codename": "disco", + "platform": "ubuntu", + "version": "19.04 (Disco Dingo)", + "family": "debian", + "name": "Ubuntu" + }, + "id": "e6f890af316a25920c951acefc46bfef", + "name": "suricata-iowa", + "containerized": false, + "ip": [ + "10.128.0.21", + "fe80::4001:aff:fe80:15" + ], + "mac": [ + "42:01:0a:80:00:15" + ], + "hostname": "suricata-iowa" + }, + "client": { + "ip": "10.128.0.21", + "port": 33814, + "packets": 2, + "bytes": 80 + }, + "user": { + "id": "0", + "name": "root" + }, + "group": { + "id": "0", + "name": "root" + }, + "server": { + "ip": "169.254.169.254", + "port": 80, + "packets": 3, + "bytes": 643 + }, + "system": { + "audit": { + "socket": { + "internal_version": "1.0.3", + "uid": 0, + "gid": 0, + "euid": 0, + "egid": 0, + "kernel_sock_address": "0xffff9e69fed14c80" + } + } + }, + "network": { + "packets": 5, + "bytes": 723, + "community_id": "1:drz3M0ZoUnQI9BgaXVRhAwCGOsw=", + "direction": "outbound", + "type": "ipv4", + "transport": "tcp" + }, + "flow": { + "final": true, + "complete": false + }, + "source": { + "ip": "10.128.0.21", + "port": 33814, + "packets": 2, + "bytes": 80 + }, + "process": { + "args": [ + "\/usr\/bin\/python3", + "\/usr\/bin\/google_network_daemon" + ], + "executable": "\/usr\/bin\/python3.7", + "created": "2019-11-04T15:01:04.190Z", + "pid": 808, + "name": "google_network_" + }, + "destination": { + "bytes": 643, + "ip": "169.254.169.254", + "port": 80, + "packets": 3 + }, + "agent": { + "version": "8.0.0", + "type": "auditbeat", + "ephemeral_id": "d59f3431-3d93-401f-b7f7-557184bbe9c6", + "hostname": "suricata-iowa", + "id": "d67ace4c-c168-4527-a349-7740d4e9dfaa" + }, + "event": { + "module": "system", + "dataset": "socket", + "kind": "signal", + "action": "network_flow", + "category": "network_traffic", + "start": "2019-11-27T00:11:34.865Z", + "end": "2019-11-27T00:11:34.866Z", + "duration": 1042842 + }, + "service": { + "type": "system" + }, + "ecs": { + "version": "1.1.0" + }, + "cloud": { + "provider": "gcp", + "instance": { + "id": "8744816015462516945", + "name": "suricata-iowa" + }, + "machine": { + "type": "n1-standard-2" + }, + "availability_zone": "us-central1-a", + "project": { + "id": "elastic-siem" + } + }, + "signal": { + "parent": { + "id": "0_Ezqm4BHe9nqdOiBktm", + "type": "event", + "index": "auditbeat-8.0.0-2019.09.06-000022", + "depth": 1 + }, + "original_time": "2019-11-27T00:11:40.012Z", + "status": "open", + "rule": { + "id": "8d9bb7f1-9d8a-4f13-ae18-ccaf4ff61893", + "rule_id": "rule-1", + "false_positives": [ + + ], + "max_signals": 100, + "risk_score": 1, + "description": "Detecting root and admin users", + "from": "now-6s", + "immutable": false, + "index": [ + "auditbeat-*", + "filebeat-*", + "packetbeat-*", + "winlogbeat-*" + ], + "interval": "5s", + "language": "kuery", + "name": "Detect Root\/Admin Users", + "query": "user.name: root or user.name: admin", + "references": [ + "http:\/\/www.example.com", + "https:\/\/ww.example.com" + ], + "severity": "high", + "tags": [ + + ], + "type": "query", + "to": "now", + "enabled": true, + "created_by": "elastic", + "updated_by": "elastic", + "threats": [ + { + "framework": "fake", + "technique": { + "reference": "techniqueRef", + "name": "techniqueName", + "id": "techniqueId" + }, + "tactic": { + "reference": "fakeRef", + "name": "fakeName", + "id": "fakeId" + } + } + ] + }, + "original_event": { + "module": "system", + "dataset": "socket", + "kind": "event", + "action": "network_flow", + "category": "network_traffic", + "start": "2019-11-27T00:11:34.865Z", + "end": "2019-11-27T00:11:34.866Z", + "duration": 1042842 + } + } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_id.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_id.sh new file mode 100755 index 0000000000000..40c198638b15a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_id.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./set_status_with_id.sh closed +# Example: ./set_status_with_id.sh open + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/status \ + -d '{"signal_ids": ["45562a28e0dFakeSignalId"], "status": "'$1'"}' \ + | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_query.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_query.sh new file mode 100755 index 0000000000000..50187c7b2fb77 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/set_status_with_query.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./sigsnals/set_status_with_query.sh closed +# Example: ./sigsnals/set_status_with_query.sh open + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/status \ + -d '{ + "status": "'$1'", + "query": {"range":{"@timestamp":{"gte":"now-2M","lte":"now/M"}}}}' \ + | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh index 8e1abc7045602..aa22db965664a 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh @@ -10,11 +10,11 @@ set -e ./check_env_variables.sh # Uses a default if no argument is specified -RULES=(${@:-./rules/root_or_admin_update_1.json}) +RULES=(${@:-./rules/updates/simplest_updated_name.json}) # Example: ./update_rule.sh -# Example: ./update_rule.sh ./rules/root_or_admin_1.json -# Example glob: ./post_rule.sh ./rules/* +# Example: ./update_rule.sh ./rules/updates/simplest_updated_name.json +# Example glob: ./post_rule.sh ./rules/updates/* for RULE in "${RULES[@]}" do { [ -e "$RULE" ] || continue diff --git a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts index dbeba598639b1..b1f0c3c4a3a18 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts @@ -521,10 +521,8 @@ describe('events elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 88c46b18d515a..38fec7fc05a3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GenericParams } from 'elasticsearch'; import * as GraphiQL from 'apollo-server-module-graphiql'; -import Boom from 'boom'; -import { ResponseToolkit } from 'hapi'; -import { EnvironmentMode } from 'kibana/public'; import { GraphQLSchema } from 'graphql'; import { runHttpQuery } from 'apollo-server-core'; -import { ServerFacade, RequestFacade } from '../../types'; +import { schema as configSchema } from '@kbn/config-schema'; +import { + CoreSetup, + IRouter, + KibanaResponseFactory, + RequestHandlerContext, + PluginInitializerContext, +} from 'src/core/server'; +import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server'; +import { RequestFacade } from '../../types'; import { FrameworkAdapter, @@ -21,125 +26,119 @@ import { WrappableRequest, } from './types'; -interface CallWithRequestParams extends GenericParams { - max_concurrent_shard_requests?: number; -} - export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; - public envMode: EnvironmentMode; + private isProductionMode: boolean; + private router: IRouter; - constructor(private server: ServerFacade, mode: EnvironmentMode) { - this.version = server.config().get('pkg.version'); - this.envMode = mode; + constructor(core: CoreSetup, env: PluginInitializerContext['env']) { + this.version = env.packageInfo.version; + this.isProductionMode = env.mode.prod; + this.router = core.http.createRouter(); } public async callWithRequest( req: FrameworkRequest, endpoint: string, - params: CallWithRequestParams, // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...rest: any[] + params: Record ) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen'); + const { elasticsearch, uiSettings } = req.context.core; + const includeFrozen = await uiSettings.client.get('search:includeFrozen'); const maxConcurrentShardRequests = endpoint === 'msearch' - ? await internalRequest.getUiSettingsService().get('courier:maxConcurrentShardRequests') + ? await uiSettings.client.get('courier:maxConcurrentShardRequests') : 0; - const fields = await callWithRequest( - internalRequest, - endpoint, - { - ...params, - ignore_throttled: !includeFrozen, - ...(maxConcurrentShardRequests > 0 - ? { max_concurrent_shard_requests: maxConcurrentShardRequests } - : {}), - }, - ...rest - ); - return fields; - } - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, + return elasticsearch.dataClient.callAsCurrentUser(endpoint, { + ...params, + ignore_throttled: !includeFrozen, + ...(maxConcurrentShardRequests > 0 + ? { max_concurrent_shard_requests: maxConcurrentShardRequests } + : {}), }); } public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { - this.server.route({ - options: { - tags: ['access:siem'], + this.router.post( + { + path: routePath, + validate: { + body: configSchema.object({ + operationName: configSchema.string(), + query: configSchema.string(), + variables: configSchema.object({}, { allowUnknowns: true }), + }), + }, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { try { - const query = - request.method === 'post' - ? (request.payload as Record) // eslint-disable-line @typescript-eslint/no-explicit-any - : (request.query as Record); // eslint-disable-line @typescript-eslint/no-explicit-any - const gqlResponse = await runHttpQuery([request], { - method: request.method.toUpperCase(), + method: 'POST', options: (req: RequestFacade) => ({ - context: { req: wrapRequest(req) }, + context: { req: wrapRequest(req, context) }, schema, }), - - query, + query: request.body, }); - return h.response(gqlResponse).type('application/json'); + return response.ok({ + body: gqlResponse, + headers: { + 'content-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'); - } + return this.handleError(error, response); + } + } + ); - const genericError = new Boom(error.message, { statusCode: error.statusCode }); + if (!this.isProductionMode) { + this.router.get( + { + path: routePath, + validate: { query: configSchema.object({}, { allowUnknowns: true }) }, + options: { + tags: ['access:siem'], + }, + }, + async (context, request, response) => { + try { + const { query } = request; + const gqlResponse = await runHttpQuery([request], { + method: 'GET', + options: (req: RequestFacade) => ({ + context: { req: wrapRequest(req, context) }, + schema, + }), + query, + }); - if (error.headers) { - Object.keys(error.headers).forEach(header => { - genericError.output.headers[header] = error.headers[header]; + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, }); + } catch (error) { + return this.handleError(error, response); } - - // Boom hides the error when status code is 500 - genericError.output.payload.message = error.message; - - throw genericError; } - }, - method: ['GET', 'POST'], - path: routePath, - vhost: undefined, - }); - - if (!this.envMode.prod) { - this.server.route({ - options: { - tags: ['access:siem'], + ); + + this.router.get( + { + path: `${routePath}/graphiql`, + validate: false, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { const graphiqlString = await GraphiQL.resolveGraphiQLString( request.query, { @@ -149,42 +148,60 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { request ); - return h.response(graphiqlString).type('text/html'); + return response.ok({ + body: graphiqlString, + headers: { + 'content-type': 'text/html', + }, + }); + } + ); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private handleError(error: any, response: KibanaResponseFactory) { + if (error.name !== 'HttpQueryError') { + return response.internalError({ + body: error.message, + headers: { + 'content-type': 'application/json', }, - method: 'GET', - path: `${routePath}/graphiql`, }); } - } - public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { - return this.server.indexPatternsServiceFactory({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => { - const fieldCaps = await this.callWithRequest( - request, - method, - { ...args, allowNoIndices: true } as GenericParams, - ...rest - ); - return fieldCaps; + return response.customError({ + statusCode: error.statusCode, + body: error.message, + headers: { + 'content-type': 'application/json', + ...error.headers, }, }); } - public getSavedObjectsService() { - return this.server.savedObjects; + public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callCluster = async (endpoint: string, params?: Record) => + this.callWithRequest(request, endpoint, { + ...params, + allowNoIndices: true, + }); + + return new IndexPatternsFetcher(callCluster); } } export function wrapRequest( - req: InternalRequest + req: InternalRequest, + context: RequestHandlerContext ): FrameworkRequest { const { auth, params, payload, query } = req; return { [internalFrameworkRequest]: req, auth, + context, params, payload, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts index ffa113a18c36c..dd31ed9fcaf5f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts @@ -7,8 +7,8 @@ import { IndicesGetMappingParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; +import { RequestHandlerContext } from 'src/core/server'; import { ESQuery } from '../../../common/typed_json'; import { PaginationInput, @@ -25,7 +25,6 @@ export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); export interface FrameworkAdapter { version: string; - exposeStaticDir(urlPath: string, dir: string): void; registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; callWithRequest( req: FrameworkRequest, @@ -37,27 +36,17 @@ export interface FrameworkAdapter { method: 'msearch', options?: object ): Promise>; - callWithRequest( - req: FrameworkRequest, - method: 'indices.existsAlias', - options?: object - ): Promise; callWithRequest( req: FrameworkRequest, method: 'indices.getMapping', options?: IndicesGetMappingParams // eslint-disable-line ): Promise; - callWithRequest( - req: FrameworkRequest, - method: 'indices.getAlias' | 'indices.get', // eslint-disable-line - options?: object - ): Promise; getIndexPatternsService(req: FrameworkRequest): FrameworkIndexPatternsService; - getSavedObjectsService(): Legacy.SavedObjectsService; } export interface FrameworkRequest { [internalFrameworkRequest]: InternalRequest; + context: RequestHandlerContext; payload: InternalRequest['payload']; params: InternalRequest['params']; query: InternalRequest['query']; @@ -132,22 +121,6 @@ export interface FrameworkIndexPatternsService { }): Promise; } -interface Alias { - settings: { - index: { - uuid: string; - }; - }; -} - -export interface DatabaseGetIndicesResponse { - [indexName: string]: { - aliases: { - [aliasName: string]: Alias; - }; - }; -} - export interface RequestBasicOptions { sourceConfiguration: SourceConfiguration; timerange: TimerangeInput; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts index fc5c037dfbb5f..0d698f1e19213 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts @@ -161,10 +161,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -184,10 +182,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -207,10 +203,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts index 291345b6befd3..48b9750ddd949 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts @@ -14,22 +14,20 @@ import { hasDocumentation, IndexAlias, } from '../../utils/beat_schema'; -import { FrameworkAdapter } from '../framework'; -import { FieldsAdapter, IndexFieldDescriptor, FrameworkFieldsRequest } from './types'; +import { FrameworkAdapter, FrameworkRequest } from '../framework'; +import { FieldsAdapter, IndexFieldDescriptor } from './types'; type IndexesAliasIndices = Record; export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexFields( - request: FrameworkFieldsRequest, - indices: string[] - ): Promise { + public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { const indexPatternsService = this.framework.getIndexPatternsService(request); const indexesAliasIndices: IndexesAliasIndices = indices.reduce( (accumulator: IndexesAliasIndices, indice: string) => { - const key: string = getIndexAlias(request.payload.variables.defaultIndex, indice); + const key = getIndexAlias(indices, indice); + if (get(key, accumulator)) { accumulator[key] = [...accumulator[key], indice]; } else { diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts index 325aaa4fa4339..a3ea8548bddc2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts @@ -6,16 +6,14 @@ import { IndexField } from '../../graphql/types'; -import { FieldsAdapter, FrameworkFieldsRequest } from './types'; +import { FieldsAdapter } from './types'; +import { FrameworkRequest } from '../framework'; export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter'; export class IndexFields { constructor(private readonly adapter: FieldsAdapter) {} - public async getFields( - request: FrameworkFieldsRequest, - defaultIndex: string[] - ): Promise { + public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise { return this.adapter.getIndexFields(request, defaultIndex); } } diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts index 719b9e8d6e18e..0c894c6980a31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts @@ -6,20 +6,9 @@ import { IndexField } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; -import { RequestFacade } from '../../types'; - -type IndexFieldsRequest = RequestFacade & { - payload: { - variables: { - defaultIndex: string[]; - }; - }; -}; - -export type FrameworkFieldsRequest = FrameworkRequest; export interface FieldsAdapter { - getIndexFields(req: FrameworkFieldsRequest, indices: string[]): Promise; + getIndexFields(req: FrameworkRequest, indices: string[]): Promise; } export interface IndexFieldDescriptor { diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts index 1c388a1808c70..4a179073852b0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts @@ -55,10 +55,8 @@ describe('getKpiHosts', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; @@ -171,10 +169,8 @@ describe('getKpiHostDetails', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts index 9c192eddf6f8a..11d007f591fac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts @@ -50,10 +50,8 @@ describe('Network Kpi elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiNetwork: ElasticsearchKpiNetworkAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts index 542a2a0108a9a..eeea4bec2fb25 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts @@ -37,9 +37,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -65,10 +63,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -107,9 +103,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -140,10 +134,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -165,9 +157,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts index 30e9ace2c0f19..e5fce811e560f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import uuid from 'uuid'; @@ -15,7 +14,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { PageInfoNote, ResponseNote, @@ -31,20 +29,11 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class Note { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deleteNote(request: FrameworkRequest, noteIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( - noteIds.map(noteId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, noteId) - ) + noteIds.map(noteId => savedObjectsClient.delete(noteSavedObjectType, noteId)) ); } @@ -55,11 +44,11 @@ export class Note { searchFields: ['timelineId'], }; const notesToBeDeleted = await this.getAllSavedNote(request, options); + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( notesToBeDeleted.notes.map(note => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, note.noteId) + savedObjectsClient.delete(noteSavedObjectType, note.noteId) ) ); } @@ -119,17 +108,17 @@ export class Note { note: SavedNote ): Promise { try { + const savedObjectsClient = request.context.core.savedObjects.client; + if (noteId == null) { const timelineVersionSavedObject = note.timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); note.timelineId = timelineResult.savedObjectId; return timelineResult.version; @@ -141,12 +130,10 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - noteSavedObjectType, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) - ), + await savedObjectsClient.create( + noteSavedObjectType, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ), }; @@ -157,16 +144,14 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - noteSavedObjectType, - noteId, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ) + await savedObjectsClient.update( + noteSavedObjectType, + noteId, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ) ), }; } catch (err) { @@ -189,20 +174,14 @@ export class Note { } private async getSavedNote(request: FrameworkRequest, NoteId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(noteSavedObjectType, NoteId); return convertSavedObjectToSavedNote(savedObject); } private async getAllSavedNote(request: FrameworkRequest, options: SavedObjectsFindOptions) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return { diff --git a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts index 75c460f0807b4..d904219c76531 100644 --- a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts @@ -38,10 +38,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -74,10 +72,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -114,10 +110,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -155,10 +149,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts index 882a139eb739a..177eb05d8539d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; @@ -14,7 +13,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { FrameworkRequest, internalFrameworkRequest } from '../framework'; import { PinnedEventSavedObject, @@ -27,24 +25,18 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class PinnedEvent { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deletePinnedEventOnTimeline(request: FrameworkRequest, pinnedEventIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( pinnedEventIds.map(pinnedEventId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEventId) ) ); } public async deleteAllPinnedEventsOnTimeline(request: FrameworkRequest, timelineId: string) { + const savedObjectsClient = request.context.core.savedObjects.client; const options: SavedObjectsFindOptions = { type: pinnedEventSavedObjectType, search: timelineId, @@ -53,9 +45,7 @@ export class PinnedEvent { const pinnedEventToBeDeleted = await this.getAllSavedPinnedEvents(request, options); await Promise.all( pinnedEventToBeDeleted.map(pinnedEvent => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) ) ); } @@ -103,18 +93,18 @@ export class PinnedEvent { eventId: string, timelineId: string | null ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (pinnedEventId == null) { const timelineVersionSavedObject = timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign return timelineResult.version; @@ -133,16 +123,14 @@ export class PinnedEvent { }; // create Pinned Event on Timeline return convertSavedObjectToSavedPinnedEvent( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - pinnedEventSavedObjectType, - pickSavedPinnedEvent( - pinnedEventId, - savedPinnedEvent, - request[internalFrameworkRequest].auth || null - ) - ), + await savedObjectsClient.create( + pinnedEventSavedObjectType, + pickSavedPinnedEvent( + pinnedEventId, + savedPinnedEvent, + request[internalFrameworkRequest].auth || null + ) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ); } @@ -177,10 +165,7 @@ export class PinnedEvent { } private async getSavedPinnedEvent(request: FrameworkRequest, pinnedEventId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(pinnedEventSavedObjectType, pinnedEventId); return convertSavedObjectToSavedPinnedEvent(savedObject); @@ -190,10 +175,7 @@ export class PinnedEvent { request: FrameworkRequest, options: SavedObjectsFindOptions ) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return savedObjects.saved_objects.map(savedObject => diff --git a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts index 3aeb137688594..17d203918d825 100644 --- a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts @@ -4,38 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DatabaseGetIndicesResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; - +import { FrameworkAdapter, FrameworkRequest } from '../framework'; import { SourceStatusAdapter } from './index'; export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexNames(request: FrameworkRequest, aliasName: string | string[]) { - const indexMaps = await Promise.all([ - this.framework - .callWithRequest(request, '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', { - index: aliasName, - filterPath: '*.settings.index.uuid', // to keep the response size as small as possible - }) - .catch(withDefaultIfNotFound({})), - ]); - return indexMaps.reduce( - (indexNames, indexMap) => [...indexNames, ...Object.keys(indexMap)], - [] as string[] - ); - } - - public async hasAlias(request: FrameworkRequest, aliasName: string): Promise { - return this.framework.callWithRequest(request, 'indices.existsAlias', { - name: aliasName, - }); - } public async hasIndices(request: FrameworkRequest, indexNames: string | string[]) { return this.framework @@ -56,13 +29,3 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { ); } } - -const withDefaultIfNotFound = (defaultValue: DefaultValue) => ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: any -): DefaultValue => { - if (error && error.status === 404) { - return defaultValue; - } - throw error; -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts index 42ff7cca19964..aa620f6cf2590 100644 --- a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts +++ b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { ConfigurationAdapter } from '../configuration'; +import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter'; import { SourcesAdapter, SourceConfiguration } from './index'; import { PartialSourceConfigurations } from './types'; @@ -15,7 +16,11 @@ interface ConfigurationWithSources { export class ConfigurationSourcesAdapter implements SourcesAdapter { private readonly configuration: ConfigurationAdapter; - constructor(configuration: ConfigurationAdapter) { + constructor( + configuration: ConfigurationAdapter< + ConfigurationWithSources + > = new InmemoryConfigurationAdapter({ sources: {} }) + ) { this.configuration = configuration; } diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts index 515920ace28d8..04c1584f1204d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; -import { Pick3 } from '../../../common/utility_types'; import { ResponseTimeline, PageInfoTimeline, @@ -34,17 +32,8 @@ interface ResponseTimelines { } export class Timeline { - private readonly note: Note; - private readonly pinnedEvent: PinnedEvent; - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) { - this.note = new Note({ savedObjects: this.libs.savedObjects }); - this.pinnedEvent = new PinnedEvent({ savedObjects: this.libs.savedObjects }); - } + private readonly note = new Note(); + private readonly pinnedEvent = new PinnedEvent(); public async getTimeline( request: FrameworkRequest, @@ -149,6 +138,8 @@ export class Timeline { version: string | null, timeline: SavedTimeline ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (timelineId == null) { // Create new timeline @@ -156,40 +147,33 @@ export class Timeline { code: 200, message: 'success', timeline: convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline( - timelineId, - timeline, - request[internalFrameworkRequest].auth || null - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline( + timelineId, + timeline, + request[internalFrameworkRequest].auth || null ) + ) ), }; } // Update Timeline - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - timelineSavedObjectType, - timelineId, - pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ); + await savedObjectsClient.update( + timelineSavedObjectType, + timelineId, + pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ); return { code: 200, message: 'success', timeline: await this.getSavedTimeline(request, timelineId), }; } catch (err) { - if ( - timelineId != null && - this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err) - ) { + if (timelineId != null && savedObjectsClient.errors.isConflictError(err)) { return { code: 409, message: err.message, @@ -212,12 +196,12 @@ export class Timeline { } public async deleteTimeline(request: FrameworkRequest, timelineIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( timelineIds.map(timelineId => Promise.all([ - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(timelineSavedObjectType, timelineId), + savedObjectsClient.delete(timelineSavedObjectType, timelineId), this.note.deleteNoteByTimelineId(request, timelineId), this.pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), ]) @@ -226,10 +210,7 @@ export class Timeline { } private async getBasicSavedTimeline(request: FrameworkRequest, timelineId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); return convertSavedObjectToSavedTimeline(savedObject); @@ -238,10 +219,7 @@ export class Timeline { private async getSavedTimeline(request: FrameworkRequest, timelineId: string) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); const timelineSaveObject = convertSavedObjectToSavedTimeline(savedObject); const timelineWithNotesAndPinnedEvents = await Promise.all([ @@ -257,10 +235,7 @@ export class Timeline { private async getAllSavedTimeline(request: FrameworkRequest, options: SavedObjectsFindOptions) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); + const savedObjectsClient = request.context.core.savedObjects.client; if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) { options.search = `${options.search != null ? options.search : ''} ${ userName != null ? convertStringToBase64(userName) : null diff --git a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts index 0ba0dfcb516fd..32a5c72215dda 100644 --- a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts @@ -24,10 +24,8 @@ describe('elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; beforeAll(async () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index c53805dc95fe7..e97a07e276dcf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ConfigType as Configuration } from '../../../../../plugins/siem/server'; import { Anomalies } from './anomalies'; import { Authentications } from './authentications'; -import { ConfigurationAdapter } from './configuration'; import { Events } from './events'; import { FrameworkAdapter, FrameworkRequest } from './framework'; import { Hosts } from './hosts'; @@ -17,7 +17,7 @@ import { KpiNetwork } from './kpi_network'; import { Network } from './network'; import { Overview } from './overview'; import { SourceStatus } from './source_status'; -import { Sources, SourceConfiguration } from './sources'; +import { Sources } from './sources'; import { UncommonProcesses } from './uncommon_processes'; import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; @@ -43,7 +43,6 @@ export interface AppDomainLibs { } export interface AppBackendLibs extends AppDomainLibs { - configuration: ConfigurationAdapter; framework: FrameworkAdapter; sources: Sources; sourceStatus: SourceStatus; @@ -52,15 +51,6 @@ export interface AppBackendLibs extends AppDomainLibs { pinnedEvent: PinnedEvent; } -export interface Configuration { - enabled: boolean; - query: { - partitionSize: number; - partitionFactor: number; - }; - sources: Record; -} - export interface SiemContext { req: FrameworkRequest; } diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 9a799a90efc65..533b6c23088ec 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -4,27 +4,71 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, EnvironmentMode, PluginInitializerContext, Logger } from 'src/core/server'; -import { ServerFacade } from './types'; -import { initServerWithKibana } from './kibana.index'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server'; +import { initServer } from './init_server'; +import { compose } from './lib/compose/kibana'; +import { + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, +} from './saved_objects'; + +export interface PluginsSetup { + features: FeaturesSetupContract; +} export class Plugin { - name = 'siem'; - private mode: EnvironmentMode; - private logger: Logger; + readonly name = 'siem'; + private readonly logger: Logger; + private context: PluginInitializerContext; - constructor({ env, logger }: PluginInitializerContext) { - this.logger = logger.get('plugins', this.name); - this.mode = env.mode; + constructor(context: PluginInitializerContext) { + this.context = context; + this.logger = context.logger.get('plugins', this.name); - this.logger.info('NP plugin initialized'); + this.logger.debug('Shim plugin initialized'); } - public setup(core: CoreSetup, dependencies: {}, __legacy: ServerFacade) { - this.logger.info('NP plugin setup'); + public setup(core: CoreSetup, plugins: PluginsSetup) { + this.logger.debug('Shim plugin setup'); - initServerWithKibana(__legacy, this.logger, this.mode); + plugins.features.registerFeature({ + id: this.name, + name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { + defaultMessage: 'SIEM', + }), + icon: 'securityAnalyticsApp', + navLinkId: 'siem', + app: ['siem', 'kibana'], + catalogue: ['siem'], + privileges: { + all: { + api: ['siem'], + savedObject: { + all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], + read: ['config'], + }, + ui: ['show'], + }, + read: { + api: ['siem'], + savedObject: { + all: [], + read: [ + 'config', + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, + ], + }, + ui: ['show'], + }, + }, + }); - this.logger.info('NP plugin setup complete'); + const libs = compose(core, this.context.env); + initServer(libs); } } diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts index ad19872b7a75d..989aaf2b6eeea 100644 --- a/x-pack/legacy/plugins/siem/server/types.ts +++ b/x-pack/legacy/plugins/siem/server/types.ts @@ -8,29 +8,21 @@ import { Legacy } from 'kibana'; export interface ServerFacade { config: Legacy.Server['config']; - getInjectedUiAppVars: Legacy.Server['getInjectedUiAppVars']; - indexPatternsServiceFactory: Legacy.Server['indexPatternsServiceFactory']; - injectUiAppVars: Legacy.Server['injectUiAppVars']; plugins: { alerting?: Legacy.Server['plugins']['alerting']; + elasticsearch: Legacy.Server['plugins']['elasticsearch']; spaces: Legacy.Server['plugins']['spaces']; - xpack_main: Legacy.Server['plugins']['xpack_main']; }; route: Legacy.Server['route']; - savedObjects: Legacy.Server['savedObjects']; } export interface RequestFacade { auth: Legacy.Request['auth']; getAlertsClient?: Legacy.Request['getAlertsClient']; getActionsClient?: Legacy.Request['getActionsClient']; - getUiSettingsService: Legacy.Request['getUiSettingsService']; headers: Legacy.Request['headers']; method: Legacy.Request['method']; params: Legacy.Request['params']; payload: unknown; query: Legacy.Request['query']; - server: { - plugins: { elasticsearch: Legacy.Request['server']['plugins']['elasticsearch'] }; - }; } diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index 4ed1937ebf782..e0db5f360f0f6 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; +} from '../../../../../../../src/legacy/core_plugins/management/public'; import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../spaces_manager'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts index 7517fa48ad8b8..0352902072790 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts index 7eddf3f4891e5..8807489157d71 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts index c1672e65326aa..67d34960ed98e 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts @@ -5,7 +5,7 @@ */ import { EventEmitter } from 'events'; import { kfetch } from 'ui/kfetch'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss index 04bc51067d9df..887495a231485 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcConfirmDeleteModal { - max-width: $euiFormMaxWidth + ($euiSizeL*2); + max-width: $euiFormMaxWidth + ($euiSizeL * 2); } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx index f461100db01db..c30792b23e3ac 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -18,6 +19,11 @@ import { spacesManagerMock } from '../../../../lib/mocks'; import { SpacesManager } from '../../../../lib'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +jest.mock('../../../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + interface SetupOpts { mockSpaces?: Space[]; returnBeforeSpacesLoad?: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx index 7461edcff10e9..1de5a10977f83 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx @@ -21,12 +21,12 @@ import { import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { - SavedObjectsManagementRecord, - processImportResponse, ProcessedImportResponse, -} from 'ui/management/saved_objects_management'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; + processImportResponse, +} from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { SpacesManager } from '../../../../lib'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx index f8d6fdf85205e..5853bebe3c669 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx @@ -5,10 +5,10 @@ */ import React, { Fragment } from 'react'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx index 1b712e84d4a05..b04c9598559b3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx @@ -5,10 +5,6 @@ */ import React, { Fragment } from 'react'; -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; import { EuiSpacer, EuiText, @@ -17,6 +13,8 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { summarizeCopyResult } from '../../../../lib/copy_saved_objects_to_space'; import { Space } from '../../../../../common/model/space'; import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx index b27be4d1715e8..f71be12276be5 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; import { SpaceAvatar } from '../../../../components'; import { Space } from '../../../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx index 43639641d541c..66ec38331c89a 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx @@ -5,10 +5,10 @@ */ import React from 'react'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; import { SummarizedCopyToSpaceResult } from 'plugins/spaces/lib/copy_saved_objects_to_space'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx index 179665ed11111..f659154c910f1 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx @@ -12,9 +12,9 @@ import { PAGE_TITLE_COMPONENT, registerSettingsComponent, } from 'ui/management'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; // @ts-ignore import routes from 'ui/routes'; +import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; import { SpacesManager } from '../../lib'; import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; import { AdvancedSettingsTitle } from './components/advanced_settings_title'; @@ -54,8 +54,8 @@ routes.defaults(/\/management/, { ); // This route resolve function executes any time the management screen is loaded, and we want to ensure // that this action is only registered once. - if (!SavedObjectsManagementActionRegistry.has(action.id)) { - SavedObjectsManagementActionRegistry.register(action); + if (!managementSetup.savedObjects.registry.has(action.id)) { + managementSetup.savedObjects.registry.register(action); } // Customize Advanced Settings diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss index 3fd2f91b25f4a..cfa2fd650e83b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss +++ b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcSpaceSelector { @include kibanaFullScreenGraphics; } @@ -27,7 +25,6 @@ @include euiBottomShadowMedium; } - .spcSpaceSelector__searchHolder { width: $euiFormMaxWidth; // make sure it's as wide as our default form element width max-width: 100%; diff --git a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts index 1facc761d6379..589803b33e11c 100644 --- a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts index 955dc831e05e2..433616e422802 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { TransformId } from './transform'; import { TransformListRow } from './transform_list'; @@ -78,8 +76,7 @@ export function getTransformProgress(item: TransformListRow) { return 100; } - const progress = idx(item, _ => _.stats.checkpointing.next.checkpoint_progress.percent_complete); - + const progress = item?.stats?.checkpointing?.next?.checkpoint_progress?.percent_complete; return progress !== undefined ? Math.round(progress) : undefined; } diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts index b40645799fb4b..62c87413c9bce 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts @@ -5,11 +5,8 @@ */ import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/public'; -import { - IndexPattern as IndexPatternType, - IndexPatterns as IndexPatternsType, -} from 'ui/index_patterns'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { IndexPattern as IndexPatternType } from 'ui/index_patterns'; +import { esQuery, IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; type IndexPatternId = string; type SavedSearchId = string; @@ -23,7 +20,7 @@ export let refreshIndexPatterns: () => Promise; export function loadIndexPatterns( savedObjectsClient: SavedObjectsClientContract, - indexPatterns: IndexPatternsType + indexPatterns: IndexPatternsContract ) { fullIndexPatterns = indexPatterns; return savedObjectsClient @@ -56,7 +53,7 @@ export function loadIndexPatterns( type CombinedQuery = Record<'bool', any> | unknown; export function loadCurrentIndexPattern( - indexPatterns: IndexPatternsType, + indexPatterns: IndexPatternsContract, indexPatternId: IndexPatternId ) { fullIndexPatterns = indexPatterns; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx index e3515991e7bb1..3a3bd33ff6d70 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx @@ -6,12 +6,11 @@ import React, { createContext, useContext, FC } from 'react'; -import { - IndexPattern as IndexPatternType, - IndexPatterns as IndexPatternsType, -} from 'ui/index_patterns'; - import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; +import { + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../../src/plugins/data/public'; import { KibanaConfig } from '../../../../../../../../src/legacy/server/kbn_server'; // set() method is missing in original d.ts @@ -25,9 +24,9 @@ interface UninitializedKibanaContextValue { export interface InitializedKibanaContextValue { combinedQuery: any; - currentIndexPattern: IndexPatternType; + currentIndexPattern: IndexPattern; currentSavedSearch: SavedSearch; - indexPatterns: IndexPatternsType; + indexPatterns: IndexPatternsContract; initialized: boolean; kbnBaseUrl: string; kibanaConfig: KibanaConfigTypeFix; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx index 7d615bfa521f0..0a9de49168ad4 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx @@ -8,8 +8,6 @@ import React, { useEffect, useState, FC } from 'react'; import { npStart } from 'ui/new_platform'; -import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; - import { useAppDependencies } from '../../app_dependencies'; import { @@ -21,7 +19,7 @@ import { import { KibanaContext, KibanaContextValue } from './kibana_context'; -const { indexPatterns } = data.indexPatterns; +const indexPatterns = npStart.plugins.data.indexPatterns; const savedObjectsClient = npStart.core.savedObjects.client; interface Props { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx index 5a5e8308b8d57..6ee03ccca16e1 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -22,6 +22,8 @@ import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/p import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils'; import { transformTableFactory } from './transform_table'; +const TransformTable = transformTableFactory(); + interface Props { transformConfig: TransformPivotConfig; } @@ -197,8 +199,6 @@ export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const transformTableLoading = previewData.length === 0 && isLoading === true; const dataTestSubj = `transformPreviewTabContent${!transformTableLoading ? ' loaded' : ''}`; - const TransformTable = transformTableFactory(); - return (
}), main: 'plugins/uptime/app', order: 8900, - url: '/app/uptime/', + url: '/app/uptime#/', }, home: ['plugins/uptime/register_feature'], }, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index 3bf1d68590ec0..0c6acb8d9f46e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -67,7 +67,7 @@ exports[`MonitorList component renders a no items message when no data is provid "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } @@ -174,7 +174,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap index 91e7b2c07070c..b7c8ddbc51b27 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap @@ -67,7 +67,7 @@ exports[`MonitorList component renders a no items message when no data is provid "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } @@ -174,7 +174,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx index 7b3b636d99b38..40a51f7c978e6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx @@ -208,7 +208,7 @@ export const MonitorListComponent = (props: Props) => { field: 'monitor_id', name: '', sortable: true, - width: '40px', + width: '24px', isExpander: true, render: (id: string) => { return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap similarity index 98% rename from x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap rename to x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 9c6d2e2f495c1..a2e31a0f33d39 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -121,8 +121,7 @@ exports[`PingList component renders sorted list without errors 1`] = ` "render": [Function], }, Object { - "align": "left", - "dataType": "number", + "align": "center", "field": "observer.geo.name", "name": "Location", "render": [Function], @@ -140,12 +139,13 @@ exports[`PingList component renders sorted list without errors 1`] = ` "render": [Function], }, Object { - "align": "left", + "align": "right", "field": "error.type", "name": "Error type", + "render": [Function], }, Object { - "align": "right", + "align": "center", "field": "http.response.status_code", "name": "Response code", "render": [Function], @@ -154,10 +154,12 @@ exports[`PingList component renders sorted list without errors 1`] = ` "align": "right", "isExpander": true, "render": [Function], - "width": "40px", + "width": "24px", }, ] } + hasActions={true} + isExpandable={true} itemId="id" itemIdToExpandedRowMap={Object {}} items={ diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/ping_list.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx similarity index 98% rename from x-pack/legacy/plugins/uptime/public/components/functional/__tests__/ping_list.test.tsx rename to x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx index 46da7e5233354..43adc4da85f32 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/ping_list.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx @@ -6,10 +6,10 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { PingResults, Ping } from '../../../../common/graphql/types'; +import { PingResults, Ping } from '../../../../../common/graphql/types'; import { PingListComponent, AllLocationOption, toggleDetails } from '../ping_list'; import { EuiComboBoxOptionProps } from '@elastic/eui'; -import { ExpandedRowMap } from '../monitor_list/types'; +import { ExpandedRowMap } from '../../monitor_list/types'; describe('PingList component', () => { let pingList: { allPings: PingResults }; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx similarity index 82% rename from x-pack/legacy/plugins/infra/public/store/remote/actions.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx index b38890afefb41..e57b229dfd973 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { logEntriesActions } from './log_entries'; +export * from './ping_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx similarity index 88% rename from x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx rename to x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index fb7c0a5af1e7f..b3c1de37c1340 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -24,13 +24,13 @@ import moment from 'moment'; import React, { Fragment, useEffect, useState } from 'react'; // @ts-ignore formatNumber import { formatNumber } from '@elastic/eui/lib/services/format'; -import { Ping, PingResults } from '../../../common/graphql/types'; -import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../lib/helper'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order'; -import { pingsQuery } from '../../queries'; -import { LocationName } from './location_name'; -import { Criteria, Pagination } from './monitor_list'; -import { PingListExpandedRowComponent } from './ping_list/expanded_row'; +import { Ping, PingResults } from '../../../../common/graphql/types'; +import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; +import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; +import { pingsQuery } from '../../../queries'; +import { LocationName } from './../location_name'; +import { Criteria, Pagination } from './../monitor_list'; +import { PingListExpandedRowComponent } from './expanded_row'; interface PingListQueryResult { allPings?: PingResults; @@ -83,6 +83,10 @@ export const PingListComponent = ({ }: Props) => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); + useEffect(() => { + onUpdateApp(); + }, [selectedOption]); + const statusOptions = [ { text: i18n.translate('xpack.uptime.pingList.statusOptions.allStatusOptionLabel', { @@ -141,8 +145,7 @@ export const PingListComponent = ({ ), }, { - align: 'left', - dataType: 'number', + align: 'center', field: 'observer.geo.name', name: i18n.translate('xpack.uptime.pingList.locationNameColumnLabel', { defaultMessage: 'Location', @@ -170,39 +173,34 @@ export const PingListComponent = ({ }), }, { - align: 'left', + align: 'right', field: 'error.type', name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', { defaultMessage: 'Error type', }), + render: (error: string) => error ?? '-', }, ]; - useEffect(() => { - onUpdateApp(); - }, [selectedOption]); - let pings: Ping[] = []; - if (data && data.allPings && data.allPings.pings) { - pings = data.allPings.pings; - const hasStatus: boolean = pings.reduce( - (hasHttpStatus: boolean, currentPing: Ping) => - hasHttpStatus || !!get(currentPing, 'http.response.status_code'), - false - ); - if (hasStatus) { - columns.push({ - field: 'http.response.status_code', - // @ts-ignore "align" property missing on type definition for column type - align: 'right', - name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { - defaultMessage: 'Response code', - }), - render: (statusCode: string) => {statusCode}, - }); - } + + const pings: Ping[] = data?.allPings?.pings ?? []; + + const hasStatus: boolean = pings.some( + (currentPing: Ping) => !!currentPing?.http?.response?.status_code + ); + if (hasStatus) { + columns.push({ + field: 'http.response.status_code', + align: 'center', + name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { + defaultMessage: 'Response code', + }), + render: (statusCode: string) => {statusCode}, + }); } + columns.push({ align: 'right', - width: '40px', + width: '24px', isExpander: true, render: (item: Ping) => ( { }, uiExports: { - hacks: [ - 'plugins/xpack_main/hacks/check_xpack_info_change', - ], replaceInjectedVars, injectDefaultVars(server) { const config = server.config(); diff --git a/x-pack/legacy/plugins/xpack_main/public/components/index.js b/x-pack/legacy/plugins/xpack_main/public/components/index.js index c18cf4665f4ac..16fdc9e7a63a7 100644 --- a/x-pack/legacy/plugins/xpack_main/public/components/index.js +++ b/x-pack/legacy/plugins/xpack_main/public/components/index.js @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LicenseStatus } from '../../../license_management/public/sections/license_dashboard/license_status/license_status'; -export { AddLicense } from '../../../license_management/public/sections/license_dashboard/add_license/add_license'; +export { + LicenseStatus +} from '../../../license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status'; + +export { AddLicense } from '../../../license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license'; /* * For to link to management diff --git a/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js b/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js deleted file mode 100644 index 8f3ae33017b39..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/public/hacks/__tests__/check_xpack_info_change.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import { banners } from 'ui/notify'; - -const XPACK_INFO_SIG_KEY = 'xpackMain.infoSignature'; -const XPACK_INFO_KEY = 'xpackMain.info'; - -describe('CheckXPackInfoChange Factory', () => { - const sandbox = sinon.createSandbox(); - - let mockSessionStorage; - beforeEach(ngMock.module('kibana', ($provide) => { - mockSessionStorage = sinon.stub({ - setItem() {}, - getItem() {}, - removeItem() {} - }); - - mockSessionStorage.getItem.withArgs(XPACK_INFO_SIG_KEY).returns('foo'); - - $provide.service('$window', () => ({ - sessionStorage: mockSessionStorage, - location: { pathname: '' } - })); - })); - - let $http; - let $httpBackend; - let $timeout; - beforeEach(ngMock.inject(($injector) => { - $http = $injector.get('$http'); - $httpBackend = $injector.get('$httpBackend'); - $timeout = $injector.get('$timeout'); - - // We set 'kbn-system-api' to not trigger other unrelated toast notifications - // like the one related to the session expiration. - $http.defaults.headers.common['kbn-system-api'] = 'x'; - - sandbox.stub(banners, 'add'); - })); - - afterEach(function () { - $httpBackend.verifyNoOutstandingRequest(); - $timeout.verifyNoPendingTasks(); - - sandbox.restore(); - }); - - it('does not show "license expired" banner if license is not expired.', () => { - const license = { license: { isActive: true, type: 'x-license' } }; - mockSessionStorage.getItem.withArgs(XPACK_INFO_KEY).returns(JSON.stringify(license)); - - $httpBackend - .when('POST', '/api/test') - .respond('ok', { 'kbn-xpack-sig': 'foo' }); - - $httpBackend - .when('GET', '/api/xpack/v1/info') - .respond(license, { 'kbn-xpack-sig': 'foo' }); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.notCalled(banners.add); - }); - - it('shows "license expired" banner if license is expired only once.', async () => { - const license = { license: { isActive: false, type: 'diamond' } }; - mockSessionStorage.getItem.withArgs(XPACK_INFO_KEY).returns(JSON.stringify(license)); - - $httpBackend - .when('POST', '/api/test') - .respond('ok', { 'kbn-xpack-sig': 'bar' }); - - $httpBackend - .when('GET', '/api/xpack/v1/info') - .respond(license, { 'kbn-xpack-sig': 'bar' }); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.calledOnce(banners.add); - - // If license didn't change banner shouldn't be displayed. - banners.add.resetHistory(); - mockSessionStorage.getItem.withArgs(XPACK_INFO_SIG_KEY).returns('bar'); - - $http.post('/api/test'); - $httpBackend.flush(); - $timeout.flush(); - - sinon.assert.notCalled(banners.add); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js b/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js deleted file mode 100644 index 587dd8cc11f55..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/public/hacks/check_xpack_info_change.js +++ /dev/null @@ -1,107 +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 { identity } from 'lodash'; -import { EuiCallOut } from '@elastic/eui'; -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; -import { banners } from 'ui/notify'; -import { DebounceProvider } from 'ui/directives/debounce'; -import { Path } from 'plugins/xpack_main/services/path'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { xpackInfoSignature } from 'plugins/xpack_main/services/xpack_info_signature'; -import { FormattedMessage } from '@kbn/i18n/react'; - -const module = uiModules.get('xpack_main', []); - -module.factory('checkXPackInfoChange', ($q, Private, $injector) => { - const debounce = Private(DebounceProvider); - const isUnauthenticated = Path.isUnauthenticated(); - let isLicenseExpirationBannerShown = false; - - const notifyIfLicenseIsExpired = debounce(() => { - const license = xpackInfo.get('license'); - if (license.isActive) { - return; - } - - const uploadLicensePath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/upload_license`; - - if (!isLicenseExpirationBannerShown) { - isLicenseExpirationBannerShown = true; - banners.add({ - component: ( - } - > - - - - ) - }} - /> - - ), - }); - } - }); - - /** - * Intercept each network response to look for the kbn-xpack-sig header. - * When that header is detected, compare its value with the value cached - * in the browser storage. When the value is new, call `xpackInfo.refresh()` - * so that it will pull down the latest x-pack info - * - * @param {object} response - the angular $http response object - * @param {function} handleResponse - callback, expects to receive the response - * @return - */ - function interceptor(response, handleResponse) { - if (isUnauthenticated) { - return handleResponse(response); - } - - const currentSignature = response.headers('kbn-xpack-sig'); - const cachedSignature = xpackInfoSignature.get(); - - if (currentSignature && cachedSignature !== currentSignature) { - // Signature from the server differ from the signature of our - // cached info, so we need to refresh it. - // Intentionally swallowing this error - // because nothing catches it and it's an ugly console error. - xpackInfo.refresh($injector).then( - () => notifyIfLicenseIsExpired(), - () => {} - ); - } - - return handleResponse(response); - } - - return { - response: (response) => interceptor(response, identity), - responseError: (response) => interceptor(response, $q.reject) - }; -}); - -module.config(($httpProvider) => { - $httpProvider.interceptors.push('checkXPackInfoChange'); -}); diff --git a/x-pack/package.json b/x-pack/package.json index 075a7867b4720..a92839eadefa4 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -176,14 +176,13 @@ "@babel/runtime": "^7.5.5", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", "@elastic/numeral": "2.3.3", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", - "@kbn/elastic-idx": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/ui-framework": "1.0.0", diff --git a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts index 4cfe581b7eac5..d1568a5ab96ce 100644 --- a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts +++ b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts @@ -17,6 +17,10 @@ export function doesInheritTimeRange(embeddable: Embeddable) { // Note: this logic might not work in a container nested world... the explicit input // may be on the root... or any of the interim parents. + // if it's a dashboard emptys screen, there will be no embeddable + if (!parent.getInput().panels[embeddable.id]) { + return false; + } // If there is no explicit input defined on the parent then this embeddable inherits the // time range from whatever the time range of the parent is. return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 00945e12db51d..a1cf2ae4e8ead 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + SavedObjectsClientContract, +} from 'src/core/server'; import { Observable, combineLatest, AsyncSubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { Server } from 'hapi'; @@ -11,6 +16,7 @@ import { once } from 'lodash'; import { Plugin as APMOSSPlugin } from '../../../../src/plugins/apm_oss/server'; import { createApmAgentConfigurationIndex } from '../../../legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index'; import { createApmApi } from '../../../legacy/plugins/apm/server/routes/create_apm_api'; +import { getApmIndices } from '../../../legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices'; import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; export interface LegacySetup { @@ -20,13 +26,18 @@ export interface LegacySetup { export interface APMPluginContract { config$: Observable; registerLegacyAPI: (__LEGACY: LegacySetup) => void; + getApmIndices: ( + savedObjectsClient: SavedObjectsClientContract + ) => ReturnType; } export class APMPlugin implements Plugin { legacySetup$: AsyncSubject; + currentConfig: APMConfig; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; this.legacySetup$ = new AsyncSubject(); + this.currentConfig = {} as APMConfig; } public async setup( @@ -49,6 +60,7 @@ export class APMPlugin implements Plugin { await new Promise(resolve => { combineLatest(mergedConfig$, core.elasticsearch.dataClient$).subscribe( async ([config, dataClient]) => { + this.currentConfig = config; await createApmAgentConfigurationIndex({ esClient: dataClient, config, @@ -64,6 +76,9 @@ export class APMPlugin implements Plugin { this.legacySetup$.next(__LEGACY); this.legacySetup$.complete(); }), + getApmIndices: async (savedObjectsClient: SavedObjectsClientContract) => { + return getApmIndices({ savedObjectsClient, config: this.currentConfig }); + }, }; } diff --git a/x-pack/plugins/endpoint/kibana.json b/x-pack/plugins/endpoint/kibana.json new file mode 100644 index 0000000000000..a7fd20b93f62d --- /dev/null +++ b/x-pack/plugins/endpoint/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "endpoint", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "endpoint"], + "requiredPlugins": ["embeddable"], + "server": true, + "ui": true +} diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/embeddable.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/embeddable.tsx new file mode 100644 index 0000000000000..55f9fd52f4662 --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/embeddable.tsx @@ -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 { + EmbeddableInput, + IContainer, + Embeddable, +} from '../../../../../../src/plugins/embeddable/public'; + +export class ResolverEmbeddable extends Embeddable { + public readonly type = 'resolver'; + constructor(initialInput: EmbeddableInput, parent?: IContainer) { + super( + // Input state is irrelevant to this embeddable, just pass it along. + initialInput, + // Initial output state - this embeddable does not do anything with output, so just + // pass along an empty object. + {}, + // Optional parent component, this embeddable can optionally be rendered inside a container. + parent + ); + } + + public render(node: HTMLElement) { + node.innerHTML = '
Welcome from Resolver
'; + } + + public reload(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts new file mode 100644 index 0000000000000..aef2e309254ef --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ResolverEmbeddable } from './'; +import { + EmbeddableFactory, + EmbeddableInput, + IContainer, +} from '../../../../../../src/plugins/embeddable/public'; + +export class ResolverEmbeddableFactory extends EmbeddableFactory { + public readonly type = 'resolver'; + + public isEditable() { + return true; + } + + public async create(initialInput: EmbeddableInput, parent?: IContainer) { + return new ResolverEmbeddable(initialInput, parent); + } + + public getDisplayName() { + return i18n.translate('xpack.endpoint.resolver.displayNameTitle', { + defaultMessage: 'Resolver', + }); + } +} diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/index.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/index.ts new file mode 100644 index 0000000000000..e4f3cc90ae30a --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ResolverEmbeddableFactory } from './factory'; +export { ResolverEmbeddable } from './embeddable'; diff --git a/x-pack/plugins/endpoint/public/index.ts b/x-pack/plugins/endpoint/public/index.ts new file mode 100644 index 0000000000000..e6a7683efd9a3 --- /dev/null +++ b/x-pack/plugins/endpoint/public/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { + EndpointPlugin, + EndpointPluginStart, + EndpointPluginSetup, + EndpointPluginStartDependencies, + EndpointPluginSetupDependencies, +} from './plugin'; + +export const plugin: PluginInitializer< + EndpointPluginSetup, + EndpointPluginStart, + EndpointPluginSetupDependencies, + EndpointPluginStartDependencies +> = () => new EndpointPlugin(); diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts new file mode 100644 index 0000000000000..21bf1b3cdea12 --- /dev/null +++ b/x-pack/plugins/endpoint/public/plugin.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; +import { IEmbeddableSetup } from 'src/plugins/embeddable/public'; +import { ResolverEmbeddableFactory } from './embeddables/resolver'; + +export type EndpointPluginStart = void; +export type EndpointPluginSetup = void; +export interface EndpointPluginSetupDependencies { + embeddable: IEmbeddableSetup; +} + +export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface + +export class EndpointPlugin + implements + Plugin< + EndpointPluginSetup, + EndpointPluginStart, + EndpointPluginSetupDependencies, + EndpointPluginStartDependencies + > { + public setup(_core: CoreSetup, plugins: EndpointPluginSetupDependencies) { + const resolverEmbeddableFactory = new ResolverEmbeddableFactory(); + plugins.embeddable.registerEmbeddableFactory( + resolverEmbeddableFactory.type, + resolverEmbeddableFactory + ); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/plugins/endpoint/server/index.ts b/x-pack/plugins/endpoint/server/index.ts new file mode 100644 index 0000000000000..f10bc7ee51b2c --- /dev/null +++ b/x-pack/plugins/endpoint/server/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { PluginInitializer } from 'src/core/server'; +import { + EndpointPlugin, + EndpointPluginStart, + EndpointPluginSetup, + EndpointPluginStartDependencies, + EndpointPluginSetupDependencies, +} from './plugin'; + +export const config = { + schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), +}; + +export const plugin: PluginInitializer< + EndpointPluginStart, + EndpointPluginSetup, + EndpointPluginStartDependencies, + EndpointPluginSetupDependencies +> = () => new EndpointPlugin(); diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts new file mode 100644 index 0000000000000..400b906c5230e --- /dev/null +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup } from 'kibana/server'; +import { addRoutes } from './routes'; + +export type EndpointPluginStart = void; +export type EndpointPluginSetup = void; +export interface EndpointPluginSetupDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface + +export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface + +export class EndpointPlugin + implements + Plugin< + EndpointPluginStart, + EndpointPluginSetup, + EndpointPluginStartDependencies, + EndpointPluginSetupDependencies + > { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + addRoutes(router); + } + + public start() {} +} diff --git a/x-pack/plugins/endpoint/server/routes/index.ts b/x-pack/plugins/endpoint/server/routes/index.ts new file mode 100644 index 0000000000000..517ee2a853660 --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/index.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 'kibana/server'; + +export function addRoutes(router: IRouter) { + router.get( + { + path: '/api/endpoint/hello-world', + validate: false, + }, + async function greetingIndex(_context, _request, response) { + return response.ok({ + body: { hello: 'world' }, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + ); +} diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json new file mode 100644 index 0000000000000..b0670a58ae1e8 --- /dev/null +++ b/x-pack/plugins/infra/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "infra", + "version": "8.0.0", + "server": true +} diff --git a/x-pack/plugins/infra/server/index.ts b/x-pack/plugins/infra/server/index.ts new file mode 100644 index 0000000000000..b12f92c8c5a9d --- /dev/null +++ b/x-pack/plugins/infra/server/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { InfraPlugin } from './plugin'; + +export const config = { + schema: schema.object({ + enabled: schema.maybe(schema.boolean()), + query: schema.object({ + partitionSize: schema.maybe(schema.number()), + partitionFactor: schema.maybe(schema.number()), + }), + }), +}; + +export const plugin = (initContext: PluginInitializerContext) => new InfraPlugin(initContext); + +export type InfraConfig = TypeOf; +export { InfraSetup } from './plugin'; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts new file mode 100644 index 0000000000000..0c763313fb973 --- /dev/null +++ b/x-pack/plugins/infra/server/plugin.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 { Plugin, PluginInitializerContext } from 'src/core/server'; + +export class InfraPlugin implements Plugin { + private readonly initContext: PluginInitializerContext; + + constructor(initContext: PluginInitializerContext) { + this.initContext = initContext; + } + + public setup() { + return { + __legacy: { + config: this.initContext.config, + }, + }; + } + + public start() {} + public stop() {} +} + +export interface InfraSetup { + /** @deprecated */ + __legacy: { + config: PluginInitializerContext['config']; + }; +} diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md new file mode 100644 index 0000000000000..339abb77ced88 --- /dev/null +++ b/x-pack/plugins/licensing/README.md @@ -0,0 +1,94 @@ +# Licensing plugin + +Retrieves license data from Elasticsearch and becomes a source of license data for all Kibana plugins on server-side and client-side. + +## API: +### Server-side + The licensing plugin retrieves license data from **Elasticsearch** at regular configurable intervals. +- `license$: Observable` Provides a steam of license data [ILicense](./common/types.ts). Plugin emits new value whenever it detects changes in license info. If the plugin cannot retrieve a license from **Elasticsearch**, it will emit `an empty license` object. +- `refresh: () => Promise` allows a plugin to enforce license retrieval. + +### Client-side + The licensing plugin retrieves license data from **licensing Kibana plugin** and does not communicate with Elasticsearch directly. +- `license$: Observable` Provides a steam of license data [ILicense](./common/types.ts). Plugin emits new value whenever it detects changes in license info. If the plugin cannot retrieve a license from **Kibana**, it will emit `an empty license` object. +- `refresh: () => Promise` allows a plugin to enforce license retrieval. + +## Migration path +The new platform licensing plugin became stateless now. It means that instead of storing all your data from `checkLicense` within the plugin, you should react on license data change on both the client and server sides. + +### Before +```ts +// my_plugin/server/plugin.ts +function checkLicense(xpackLicenseInfo: XPackInfo){ + if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { + return { + isAvailable: false, + showLinks: true, + } + } + if (!xpackLicenseInfo.feature('name').isEnabled()) { + return { + isAvailable: false, + showLinks: false, + } + } + const hasRequiredLicense = xPackInfo.license.isOneOf([ + 'gold', + 'platinum', + 'trial', + ]); + return { + isAvailable: hasRequiredLicense, + showLinks: hasRequiredLicense, + } +} +xpackMainPlugin.info.feature(pluginId).registerLicenseCheckResultsGenerator(checkLicense); + +// my_plugin/client/plugin.ts +chrome.navLinks.update('myPlugin', { + hidden: !xpackInfo.get('features.myPlugin.showLinks', false) +}); +``` + +### After +```ts +// kibana.json +"requiredPlugins": ["licensing"], + +// my_plugin/server/plugin.ts +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing' + +interface SetupDeps { + licensing: LicensingPluginSetup; +} + +class MyPlugin { + setup(core: CoreSetup, deps: SetupDeps) { + deps.licensing.license$.subscribe(license => { + const { state, message } = license.check('myPlugin', 'gold') + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + if (hasRequiredLicense && license.getFeature('name').isAvailable) { + // enable some server side logic + } else { + log(message); + // disable some server side logic + } + }) + } +} + +// my_plugin/client/plugin.ts +class MyPlugin { + setup(core: CoreSetup, deps: SetupDeps) { + deps.licensing.license$.subscribe(license => { + const { state, message } = license.check('myPlugin', 'gold') + const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const showLinks = hasRequiredLicense && license.getFeature('name').isAvailable; + + chrome.navLinks.update('myPlugin', { + hidden: !showLinks + }); + }) + } +} +``` diff --git a/x-pack/plugins/licensing/common/license_update.test.ts b/x-pack/plugins/licensing/common/license_update.test.ts index 345085d3e3a8f..68660eaf2d713 100644 --- a/x-pack/plugins/licensing/common/license_update.test.ts +++ b/x-pack/plugins/licensing/common/license_update.test.ts @@ -12,7 +12,7 @@ import { createLicenseUpdate } from './license_update'; import { licenseMock } from './license.mock'; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - +const stop$ = new Subject(); describe('licensing update', () => { it('loads updates when triggered', async () => { const types: LicenseType[] = ['basic', 'gold']; @@ -24,16 +24,16 @@ describe('licensing update', () => { Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) ); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); expect(fetcher).toHaveBeenCalledTimes(0); trigger$.next(); - const first = await update$.pipe(take(1)).toPromise(); + const first = await license$.pipe(take(1)).toPromise(); expect(first.type).toBe('basic'); trigger$.next(); - const [, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(second.type).toBe('gold'); }); @@ -43,9 +43,9 @@ describe('licensing update', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(fetchedLicense); - const { update$ } = createLicenseUpdate(trigger$, fetcher, initialLicense); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher, initialLicense); trigger$.next(); - const [first, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [first, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(first.type).toBe('platinum'); expect(second.type).toBe('gold'); @@ -64,17 +64,17 @@ describe('licensing update', () => { ) ); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); trigger$.next(); - const [first] = await update$.pipe(take(1), toArray()).toPromise(); + const [first] = await license$.pipe(take(1), toArray()).toPromise(); expect(first.type).toBe('basic'); trigger$.next(); trigger$.next(); - const [, second] = await update$.pipe(take(2), toArray()).toPromise(); + const [, second] = await license$.pipe(take(2), toArray()).toPromise(); expect(second.type).toBe('gold'); expect(fetcher).toHaveBeenCalledTimes(3); @@ -85,11 +85,11 @@ describe('licensing update', () => { const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); - update$.subscribe(() => {}); - update$.subscribe(() => {}); - update$.subscribe(() => {}); + license$.subscribe(() => {}); + license$.subscribe(() => {}); + license$.subscribe(() => {}); trigger$.next(); expect(fetcher).toHaveBeenCalledTimes(1); @@ -110,9 +110,9 @@ describe('licensing update', () => { }) ); const trigger$ = new Subject(); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; - update$.subscribe(license => values.push(license)); + license$.subscribe(license => values.push(license)); trigger$.next(); trigger$.next(); @@ -124,29 +124,58 @@ describe('licensing update', () => { await expect(values[0].type).toBe('gold'); }); - it('completes update$ stream when trigger is completed', () => { + it('completes license$ stream when stop$ is triggered', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$ } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); let completed = false; - update$.subscribe({ complete: () => (completed = true) }); + license$.subscribe({ complete: () => (completed = true) }); - trigger$.complete(); + stop$.next(); expect(completed).toBe(true); }); - it('stops fetching when fetch subscription unsubscribed', () => { + it('stops fetching when stop$ is triggered', () => { const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); - const { update$, fetchSubscription } = createLicenseUpdate(trigger$, fetcher); + const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; - update$.subscribe(license => values.push(license)); + license$.subscribe(license => values.push(license)); - fetchSubscription.unsubscribe(); + stop$.next(); trigger$.next(); expect(fetcher).toHaveBeenCalledTimes(0); }); + + it('refreshManually guarantees license fetching', async () => { + const trigger$ = new Subject(); + const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + + const fetcher = jest + .fn() + .mockImplementationOnce(async () => { + await delay(100); + return firstLicense; + }) + .mockImplementationOnce(async () => { + await delay(100); + return secondLicense; + }); + + const { license$, refreshManually } = createLicenseUpdate(trigger$, stop$, fetcher); + let fromObservable; + license$.subscribe(license => (fromObservable = license)); + + const licenseResult = await refreshManually(); + expect(licenseResult.uid).toBe('first'); + expect(licenseResult).toBe(fromObservable); + + const secondResult = await refreshManually(); + expect(secondResult.uid).toBe('second'); + expect(secondResult).toBe(fromObservable); + }); }); diff --git a/x-pack/plugins/licensing/common/license_update.ts b/x-pack/plugins/licensing/common/license_update.ts index 254ea680460ee..0197ca5396ad1 100644 --- a/x-pack/plugins/licensing/common/license_update.ts +++ b/x-pack/plugins/licensing/common/license_update.ts @@ -3,36 +3,45 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ConnectableObservable, Observable, from, merge } from 'rxjs'; +import { ConnectableObservable, Observable, Subject, from, merge } from 'rxjs'; -import { filter, map, pairwise, switchMap, publishReplay } from 'rxjs/operators'; +import { filter, map, pairwise, switchMap, publishReplay, takeUntil } from 'rxjs/operators'; import { hasLicenseInfoChanged } from './has_license_info_changed'; import { ILicense } from './types'; export function createLicenseUpdate( trigger$: Observable, + stop$: Observable, fetcher: () => Promise, initialValues?: ILicense ) { - const fetched$ = trigger$.pipe( - switchMap(fetcher), + const triggerRefresh$ = trigger$.pipe(switchMap(fetcher)); + const manuallyFetched$ = new Subject(); + + const fetched$ = merge(triggerRefresh$, manuallyFetched$).pipe( + takeUntil(stop$), publishReplay(1) // have to cast manually as pipe operator cannot return ConnectableObservable // https://github.com/ReactiveX/rxjs/issues/2972 ) as ConnectableObservable; const fetchSubscription = fetched$.connect(); + stop$.subscribe({ complete: () => fetchSubscription.unsubscribe() }); const initialValues$ = initialValues ? from([undefined, initialValues]) : from([undefined]); - const update$: Observable = merge(initialValues$, fetched$).pipe( + const license$: Observable = merge(initialValues$, fetched$).pipe( pairwise(), filter(([previous, next]) => hasLicenseInfoChanged(previous, next!)), map(([, next]) => next!) ); return { - update$, - fetchSubscription, + license$, + async refreshManually() { + const license = await fetcher(); + manuallyFetched$.next(license); + return license; + }, }; } diff --git a/x-pack/plugins/licensing/common/types.ts b/x-pack/plugins/licensing/common/types.ts index c8edd8fd0cca8..c5d838d23d8c3 100644 --- a/x-pack/plugins/licensing/common/types.ts +++ b/x-pack/plugins/licensing/common/types.ts @@ -183,5 +183,5 @@ export interface LicensingPluginSetup { /** * Triggers licensing information re-fetch. */ - refresh(): void; + refresh(): Promise; } diff --git a/x-pack/plugins/licensing/public/expired_banner.tsx b/x-pack/plugins/licensing/public/expired_banner.tsx new file mode 100644 index 0000000000000..9c42d3ff6997d --- /dev/null +++ b/x-pack/plugins/licensing/public/expired_banner.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; + +interface Props { + type: string; + uploadUrl: string; +} + +const ExpiredBanner: React.FunctionComponent = props => ( + + } + > + + + + ), + }} + /> + +); + +export const mountExpiredBanner = (props: Props) => + toMountPoint(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/index.ts b/x-pack/plugins/licensing/public/plugin.test.mocks.ts similarity index 63% rename from x-pack/legacy/plugins/infra/public/store/remote/index.ts rename to x-pack/plugins/licensing/public/plugin.test.mocks.ts index c2843320bfd0c..6635a7950c0e8 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/index.ts +++ b/x-pack/plugins/licensing/public/plugin.test.mocks.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './actions'; -export * from './epic'; -export * from './reducer'; -export * from './selectors'; +export const mountExpiredBannerMock = jest.fn(); +jest.doMock('./expired_banner', () => ({ + mountExpiredBanner: mountExpiredBannerMock, +})); diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 60dfd1f6cb260..c356f7f5df184 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -5,6 +5,7 @@ */ import { take } from 'rxjs/operators'; +import { mountExpiredBannerMock } from './plugin.test.mocks'; import { LicenseType } from '../common/types'; import { LicensingPlugin, licensingSessionStorageKey } from './plugin'; @@ -14,10 +15,13 @@ import { licenseMock } from '../common/license.mock'; import { coreMock } from '../../../../src/core/public/mocks'; import { HttpInterceptor } from 'src/core/public'; +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); + describe('licensing plugin', () => { let plugin: LicensingPlugin; afterEach(async () => { + jest.clearAllMocks(); await plugin.stop(); }); @@ -28,15 +32,30 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create({ license: { uid: 'fetched' } }); - coreSetup.http.get.mockResolvedValue(fetchedLicense); + const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + coreSetup.http.get + .mockImplementationOnce(async () => { + await delay(100); + return firstLicense; + }) + .mockImplementationOnce(async () => { + await delay(100); + return secondLicense; + }); const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); - const license = await license$.pipe(take(1)).toPromise(); + let fromObservable; + license$.subscribe(license => (fromObservable = license)); - expect(license.uid).toBe('fetched'); + const licenseResult = await refresh(); + expect(licenseResult.uid).toBe('first'); + expect(licenseResult).toBe(fromObservable); + + const secondResult = await refresh(); + expect(secondResult.uid).toBe('second'); + expect(secondResult).toBe(fromObservable); }); it('data re-fetch call marked as a system api', async () => { @@ -49,7 +68,7 @@ describe('licensing plugin', () => { const { refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); expect(coreSetup.http.get.mock.calls[0][1]).toMatchObject({ headers: { @@ -119,7 +138,7 @@ describe('licensing plugin', () => { const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); const license = await license$.pipe(take(1)).toPromise(); expect(license.uid).toBe('fresh'); @@ -143,7 +162,7 @@ describe('licensing plugin', () => { coreSetup.http.get.mockRejectedValue(new Error('reason')); const { license$, refresh } = await plugin.setup(coreSetup); - refresh(); + await refresh(); const license = await license$.pipe(take(1)).toPromise(); @@ -161,7 +180,7 @@ describe('licensing plugin', () => { const { license$, refresh } = await plugin.setup(coreSetup); expect(sessionStorage.removeItem).toHaveBeenCalledTimes(0); - refresh(); + await refresh(); await license$.pipe(take(1)).toPromise(); expect(sessionStorage.removeItem).toHaveBeenCalledTimes(1); @@ -169,6 +188,7 @@ describe('licensing plugin', () => { }); }); }); + describe('interceptor', () => { it('register http interceptor checking signature header', async () => { const sessionStorage = coreMock.createStorage(); @@ -201,7 +221,7 @@ describe('licensing plugin', () => { response: { headers: { get(name: string) { - if (name === 'kbn-xpack-sig') { + if (name === 'kbn-license-sig') { return 'signature-1'; } throw new Error('unexpected header'); @@ -226,6 +246,40 @@ describe('licensing plugin', () => { expect(coreSetup.http.get).toHaveBeenCalledTimes(1); }); + it('http interceptor does not trigger license re-fetch for anonymous pages', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + + const coreSetup = coreMock.createSetup(); + coreSetup.http.anonymousPaths.isAnonymous.mockReturnValue(true); + + let registeredInterceptor: HttpInterceptor; + coreSetup.http.intercept.mockImplementation((interceptor: HttpInterceptor) => { + registeredInterceptor = interceptor; + return () => undefined; + }); + + await plugin.setup(coreSetup); + const httpResponse = { + response: { + headers: { + get(name: string) { + if (name === 'kbn-license-sig') { + return 'signature-1'; + } + throw new Error('unexpected header'); + }, + }, + }, + request: { + url: 'http://10.10.10.10:5601/api/hello', + }, + }; + await registeredInterceptor!.response!(httpResponse as any, null as any); + + expect(coreSetup.http.get).toHaveBeenCalledTimes(0); + }); + it('http interceptor does not trigger re-fetch if requested x-pack/info endpoint', async () => { const sessionStorage = coreMock.createStorage(); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); @@ -249,7 +303,7 @@ describe('licensing plugin', () => { response: { headers: { get(name: string) { - if (name === 'kbn-xpack-sig') { + if (name === 'kbn-license-sig') { return 'signature-1'; } throw new Error('unexpected header'); @@ -269,31 +323,71 @@ describe('licensing plugin', () => { expect(updated).toBe(false); }); }); - describe('#stop', () => { - it('stops polling', async () => { + + describe('expired banner', () => { + it('does not show "license expired" banner if license is not expired.', async () => { const sessionStorage = coreMock.createStorage(); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + const coreSetup = coreMock.createSetup(); - const { license$ } = await plugin.setup(coreSetup); + coreSetup.http.get.mockResolvedValueOnce( + licenseMock.create({ license: { status: 'active', type: 'gold' } }) + ); - let completed = false; - license$.subscribe({ complete: () => (completed = true) }); + const { refresh } = await plugin.setup(coreSetup); - await plugin.stop(); - expect(completed).toBe(true); + const coreStart = coreMock.createStart(); + await plugin.start(coreStart); + + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(0); }); - it('refresh does not trigger data re-fetch', async () => { + it('shows "license expired" banner if license is expired only once.', async () => { const sessionStorage = coreMock.createStorage(); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + const coreSetup = coreMock.createSetup(); + const activeLicense = licenseMock.create({ license: { status: 'active', type: 'gold' } }); + const expiredLicense = licenseMock.create({ license: { status: 'expired', type: 'gold' } }); + coreSetup.http.get + .mockResolvedValueOnce(activeLicense) + .mockResolvedValueOnce(expiredLicense) + .mockResolvedValueOnce(activeLicense) + .mockResolvedValueOnce(expiredLicense); + const { refresh } = await plugin.setup(coreSetup); - await plugin.stop(); + const coreStart = coreMock.createStart(); + await plugin.start(coreStart); + + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(0); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + await refresh(); + expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); + expect(mountExpiredBannerMock).toHaveBeenCalledWith({ + type: 'gold', + uploadUrl: '/app/kibana#/management/elasticsearch/license_management/upload_license', + }); + }); + }); + + describe('#stop', () => { + it('stops polling', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + const coreSetup = coreMock.createSetup(); + const { license$ } = await plugin.setup(coreSetup); - refresh(); + let completed = false; + license$.subscribe({ complete: () => (completed = true) }); - expect(coreSetup.http.get).toHaveBeenCalledTimes(0); + await plugin.stop(); + expect(completed).toBe(true); }); it('removes http interceptor', async () => { diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 79ad6f289b67e..c0dc0f21b90be 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -3,15 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Subject, Subscription } from 'rxjs'; -import { Subject, Subscription, merge } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { ILicense, LicensingPluginSetup } from '../common/types'; import { createLicenseUpdate } from '../common/license_update'; import { License } from '../common/license'; +import { mountExpiredBanner } from './expired_banner'; export const licensingSessionStorageKey = 'xpack.licensing'; @@ -30,10 +29,11 @@ export class LicensingPlugin implements Plugin { * A function to execute once the plugin's HTTP interceptor needs to stop listening. */ private removeInterceptor?: () => void; - private licenseFetchSubscription?: Subscription; - private storageSubscription?: Subscription; + private internalSubscription?: Subscription; + private isLicenseExpirationBannerShown? = false; private readonly infoEndpoint = '/api/licensing/info'; + private coreStart?: CoreStart; private prevSignature?: string; constructor( @@ -65,19 +65,16 @@ export class LicensingPlugin implements Plugin { } public setup(core: CoreSetup) { - const manualRefresh$ = new Subject(); const signatureUpdated$ = new Subject(); - const refresh$ = merge(signatureUpdated$, manualRefresh$).pipe(takeUntil(this.stop$)); - const savedLicense = this.getSaved(); - const { update$, fetchSubscription } = createLicenseUpdate( - refresh$, + const { license$, refreshManually } = createLicenseUpdate( + signatureUpdated$, + this.stop$, () => this.fetchLicense(core), - savedLicense + this.getSaved() ); - this.licenseFetchSubscription = fetchSubscription; - this.storageSubscription = update$.subscribe(license => { + this.internalSubscription = license$.subscribe(license => { if (license.isAvailable) { this.prevSignature = license.signature; this.save(license); @@ -86,12 +83,19 @@ export class LicensingPlugin implements Plugin { // Prevent reusing stale license if the fetch operation fails this.removeSaved(); } + + if (license.status === 'expired' && !this.isLicenseExpirationBannerShown && this.coreStart) { + this.isLicenseExpirationBannerShown = true; + this.showExpiredBanner(license); + } }); this.removeInterceptor = core.http.intercept({ response: async httpResponse => { + // we don't track license as anon users do not have one. + if (core.http.anonymousPaths.isAnonymous(window.location.pathname)) return httpResponse; if (httpResponse.response) { - const signatureHeader = httpResponse.response.headers.get('kbn-xpack-sig'); + const signatureHeader = httpResponse.response.headers.get('kbn-license-sig'); if (this.prevSignature !== signatureHeader) { if (!httpResponse.request!.url.includes(this.infoEndpoint)) { signatureUpdated$.next(); @@ -103,14 +107,14 @@ export class LicensingPlugin implements Plugin { }); return { - refresh: () => { - manualRefresh$.next(); - }, - license$: update$, + refresh: refreshManually, + license$, }; } - public async start() {} + public async start(core: CoreStart) { + this.coreStart = core; + } public stop() { this.stop$.next(); @@ -119,13 +123,9 @@ export class LicensingPlugin implements Plugin { if (this.removeInterceptor !== undefined) { this.removeInterceptor(); } - if (this.licenseFetchSubscription !== undefined) { - this.licenseFetchSubscription.unsubscribe(); - this.licenseFetchSubscription = undefined; - } - if (this.storageSubscription !== undefined) { - this.storageSubscription.unsubscribe(); - this.storageSubscription = undefined; + if (this.internalSubscription !== undefined) { + this.internalSubscription.unsubscribe(); + this.internalSubscription = undefined; } } @@ -136,7 +136,6 @@ export class LicensingPlugin implements Plugin { 'kbn-system-api': 'true', }, }); - return new License({ license: response.license, features: response.features, @@ -146,4 +145,16 @@ export class LicensingPlugin implements Plugin { return new License({ error: error.message, signature: '' }); } }; + + private showExpiredBanner(license: ILicense) { + const uploadUrl = this.coreStart!.http.basePath.prepend( + '/app/kibana#/management/elasticsearch/license_management/upload_license' + ); + this.coreStart!.overlays.banners.add( + mountExpiredBanner({ + type: license.type!, + uploadUrl, + }) + ); + } } diff --git a/x-pack/plugins/licensing/server/licensing_config.ts b/x-pack/plugins/licensing/server/licensing_config.ts index 7be19398828e9..6cb3e8d9ef3a1 100644 --- a/x-pack/plugins/licensing/server/licensing_config.ts +++ b/x-pack/plugins/licensing/server/licensing_config.ts @@ -6,10 +6,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; -const SECOND = 1000; export const config = { schema: schema.object({ - pollingFrequency: schema.number({ defaultValue: 30 * SECOND }), + pollingFrequency: schema.duration({ defaultValue: '30s' }), }), }; diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts new file mode 100644 index 0000000000000..4251e72accc9f --- /dev/null +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BehaviorSubject } from 'rxjs'; +import { createOnPreResponseHandler } from './on_pre_response_handler'; +import { httpServiceMock, httpServerMock } from '../../../../src/core/server/mocks'; +import { licenseMock } from '../common/license.mock'; + +describe('createOnPreResponseHandler', () => { + it('sets license.signature header immediately for non-error responses', async () => { + const refresh = jest.fn(); + const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const toolkit = httpServiceMock.createOnPreResponseToolkit(); + + const interceptor = createOnPreResponseHandler(refresh, license$); + await interceptor(httpServerMock.createKibanaRequest(), { statusCode: 200 }, toolkit); + + expect(refresh).toHaveBeenCalledTimes(0); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-license-sig': 'foo', + }, + }); + }); + it('sets license.signature header after refresh for non-error responses', async () => { + const updatedLicense = licenseMock.create({ signature: 'bar' }); + const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const refresh = jest.fn().mockImplementation( + () => + new Promise(resolve => { + setTimeout(() => { + license$.next(updatedLicense); + resolve(); + }, 50); + }) + ); + + const toolkit = httpServiceMock.createOnPreResponseToolkit(); + + const interceptor = createOnPreResponseHandler(refresh, license$); + await interceptor(httpServerMock.createKibanaRequest(), { statusCode: 400 }, toolkit); + + expect(refresh).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-license-sig': 'bar', + }, + }); + }); +}); diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.ts new file mode 100644 index 0000000000000..c8befceb4fe32 --- /dev/null +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { OnPreResponseHandler } from '../../../../src/core/server'; +import { ILicense } from '../common/types'; + +export function createOnPreResponseHandler( + refresh: () => Promise, + license$: Observable +): OnPreResponseHandler { + return async (req, res, t) => { + // If we're returning an error response, refresh license info from + // Elasticsearch in case the error is due to a change in license information + // in Elasticsearch. + // https://github.com/elastic/x-pack-kibana/pull/2876 + if (res.statusCode >= 400) { + await refresh(); + } + const license = await license$.pipe(take(1)).toPromise(); + return t.next({ + headers: { + 'kbn-license-sig': license.signature, + }, + }); + }; +} diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index 2af3637a2aaf0..62b6ec6a106b7 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -6,6 +6,7 @@ import { BehaviorSubject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; +import moment from 'moment'; import { LicenseType } from '../common/types'; import { ElasticsearchError, RawLicense } from './types'; import { LicensingPlugin } from './plugin'; @@ -24,7 +25,7 @@ function buildRawLicense(options: Partial = {}): RawLicense { }; return Object.assign(defaultRawLicense, options); } -const pollingFrequency = 100; +const pollingFrequency = moment.duration(100); const flushPromises = (ms = 50) => new Promise(res => setTimeout(res, ms)); @@ -199,7 +200,7 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin( coreMock.createPluginInitializerContext({ // disable polling mechanism - pollingFrequency: 50000, + pollingFrequency: moment.duration(50000), }) ); const dataClient = elasticsearchServiceMock.createClusterClient(); @@ -251,6 +252,26 @@ describe('licensing plugin', () => { `); }); }); + + describe('registers on pre-response interceptor', () => { + let plugin: LicensingPlugin; + + beforeEach(() => { + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext({ pollingFrequency })); + }); + + afterEach(async () => { + await plugin.stop(); + }); + + it('once', async () => { + const coreSetup = coreMock.createSetup(); + + await plugin.setup(coreSetup); + + expect(coreSetup.http.registerOnPreResponse).toHaveBeenCalledTimes(1); + }); + }); }); describe('#stop', () => { @@ -269,31 +290,5 @@ describe('licensing plugin', () => { await plugin.stop(); expect(completed).toBe(true); }); - - it('refresh does not trigger data re-fetch', async () => { - const plugin = new LicensingPlugin( - coreMock.createPluginInitializerContext({ - pollingFrequency, - }) - ); - - const dataClient = elasticsearchServiceMock.createClusterClient(); - dataClient.callAsInternalUser.mockResolvedValue({ - license: buildRawLicense(), - features: {}, - }); - - const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); - - const { refresh } = await plugin.setup(coreSetup); - - dataClient.callAsInternalUser.mockClear(); - - await plugin.stop(); - refresh(); - - expect(dataClient.callAsInternalUser).toHaveBeenCalledTimes(0); - }); }); }); diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index d3dc84c05e25c..64f7cc56948f2 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable, Subject, Subscription, merge, timer } from 'rxjs'; -import { take, takeUntil } from 'rxjs/operators'; -import moment from 'moment'; +import { Observable, Subject, Subscription, timer } from 'rxjs'; +import { take } from 'rxjs/operators'; +import moment, { Duration } from 'moment'; import { createHash } from 'crypto'; import stringify from 'json-stable-stringify'; @@ -28,6 +28,7 @@ import { registerRoutes } from './routes'; import { LicenseConfigType } from './licensing_config'; import { createRouteHandlerContext } from './licensing_route_handler_context'; +import { createOnPreResponseHandler } from './on_pre_response_handler'; function normalizeServerLicense(license: RawLicense): PublicLicense { return { @@ -78,7 +79,6 @@ export class LicensingPlugin implements Plugin { private stop$ = new Subject(); private readonly logger: Logger; private readonly config$: Observable; - private licenseFetchSubscription?: Subscription; private loggingSubscription?: Subscription; constructor(private readonly context: PluginInitializerContext) { @@ -94,7 +94,9 @@ export class LicensingPlugin implements Plugin { const { refresh, license$ } = this.createLicensePoller(dataClient, config.pollingFrequency); core.http.registerRouteHandlerContext('licensing', createRouteHandlerContext(license$)); + registerRoutes(core.http.createRouter()); + core.http.registerOnPreResponse(createOnPreResponseHandler(refresh, license$)); return { refresh, @@ -102,17 +104,14 @@ export class LicensingPlugin implements Plugin { }; } - private createLicensePoller(clusterClient: IClusterClient, pollingFrequency: number) { - const manualRefresh$ = new Subject(); - const intervalRefresh$ = timer(0, pollingFrequency); - const refresh$ = merge(intervalRefresh$, manualRefresh$).pipe(takeUntil(this.stop$)); + private createLicensePoller(clusterClient: IClusterClient, pollingFrequency: Duration) { + const intervalRefresh$ = timer(0, pollingFrequency.asMilliseconds()); - const { update$, fetchSubscription } = createLicenseUpdate(refresh$, () => + const { license$, refreshManually } = createLicenseUpdate(intervalRefresh$, this.stop$, () => this.fetchLicense(clusterClient) ); - this.licenseFetchSubscription = fetchSubscription; - this.loggingSubscription = update$.subscribe(license => + this.loggingSubscription = license$.subscribe(license => this.logger.debug( 'Imported license information from Elasticsearch:' + [ @@ -124,11 +123,11 @@ export class LicensingPlugin implements Plugin { ); return { - refresh: () => { + refresh: async () => { this.logger.debug('Requesting Elasticsearch licensing API'); - manualRefresh$.next(); + return await refreshManually(); }, - license$: update$, + license$, }; } @@ -139,8 +138,13 @@ export class LicensingPlugin implements Plugin { path: '/_xpack', }); - const normalizedLicense = normalizeServerLicense(response.license); - const normalizedFeatures = normalizeFeatures(response.features); + const normalizedLicense = response.license + ? normalizeServerLicense(response.license) + : undefined; + const normalizedFeatures = response.features + ? normalizeFeatures(response.features) + : undefined; + const signature = sign({ license: normalizedLicense, features: normalizedFeatures, @@ -179,11 +183,6 @@ export class LicensingPlugin implements Plugin { this.stop$.next(); this.stop$.complete(); - if (this.licenseFetchSubscription !== undefined) { - this.licenseFetchSubscription.unsubscribe(); - this.licenseFetchSubscription = undefined; - } - if (this.loggingSubscription !== undefined) { this.loggingSubscription.unsubscribe(); this.loggingSubscription = undefined; diff --git a/x-pack/plugins/security/public/session/session_timeout.test.tsx b/x-pack/plugins/security/public/session/session_timeout.test.tsx index eb947ab95c43b..eca3e7d6727df 100644 --- a/x-pack/plugins/security/public/session/session_timeout.test.tsx +++ b/x-pack/plugins/security/public/session/session_timeout.test.tsx @@ -124,7 +124,7 @@ describe('Session Timeout', () => { }); test(`starts and does not initialize on an anonymous path`, async () => { - http.anonymousPaths.register(window.location.pathname); + http.anonymousPaths.isAnonymous.mockReturnValue(true); await sessionTimeout.start(); // eslint-disable-next-line dot-notation expect(sessionTimeout['channel']).toBeUndefined(); diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index 888565cd7e0ff..c18c071ca7675 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient, Logger } from '../../../../../target/types/core/server'; +import { IClusterClient, Logger } from '../../../../../src/core/server'; import { RawKibanaPrivileges } from '../../common/model'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; diff --git a/x-pack/plugins/siem/kibana.json b/x-pack/plugins/siem/kibana.json new file mode 100644 index 0000000000000..2bc33b87a1b43 --- /dev/null +++ b/x-pack/plugins/siem/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "siem", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "siem"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/siem/server/config.ts b/x-pack/plugins/siem/server/config.ts new file mode 100644 index 0000000000000..456646cc825f3 --- /dev/null +++ b/x-pack/plugins/siem/server/config.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { + SIGNALS_INDEX_KEY, + DEFAULT_SIGNALS_INDEX, +} from '../../../legacy/plugins/siem/common/constants'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + [SIGNALS_INDEX_KEY]: schema.string({ defaultValue: DEFAULT_SIGNALS_INDEX }), +}); + +export const createConfig$ = (context: PluginInitializerContext) => + context.config.create>(); + +export type ConfigType = ReturnType extends Observable + ? T + : ReturnType; diff --git a/x-pack/plugins/siem/server/index.ts b/x-pack/plugins/siem/server/index.ts new file mode 100644 index 0000000000000..c675be691b47e --- /dev/null +++ b/x-pack/plugins/siem/server/index.ts @@ -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 { PluginInitializerContext } from 'src/core/server'; +import { Plugin } from './plugin'; +import { configSchema, ConfigType } from './config'; + +export const plugin = (context: PluginInitializerContext) => { + return new Plugin(context); +}; + +export const config = { schema: configSchema }; + +export { ConfigType }; diff --git a/x-pack/plugins/siem/server/plugin.ts b/x-pack/plugins/siem/server/plugin.ts new file mode 100644 index 0000000000000..866f4d7575e2f --- /dev/null +++ b/x-pack/plugins/siem/server/plugin.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; + +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { createConfig$, ConfigType } from './config'; + +export class Plugin { + readonly name = 'siem'; + private readonly logger: Logger; + // @ts-ignore-next-line TODO(rylnd): use it or lose it + private readonly config$: Observable; + + constructor(context: PluginInitializerContext) { + const { logger } = context; + this.logger = logger.get(); + this.logger.debug('plugin initialized'); + + this.config$ = createConfig$(context); + } + + public setup(core: CoreSetup, plugins: {}) { + this.logger.debug('plugin setup'); + } + + public start() { + this.logger.debug('plugin started'); + } + + public stop() { + this.logger.debug('plugin stopped'); + } +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c24f04697e3e5..55147c7863d1f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "データに接続", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", + "management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", + "management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", + "management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", "common.ui.management.nav.menu": "管理メニュー", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", @@ -676,6 +676,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "{objectType} を保存", "kibana-react.savedObjects.saveModal.titleLabel": "タイトル", + "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", + "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", "inspector.closeButton": "インスペクターを閉じる", "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", "inspector.reqTimestampKey": "リクエストのタイムスタンプ", @@ -6394,9 +6396,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "戻る", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 引数には id プロパティを含める必要があります", "xpack.logstash.workersTooltip": "パイプラインのフィルターとアウトプットステージを同時に実行するワーカーの数です。イベントが詰まってしまう場合や CPU が飽和状態ではない場合は、マシンの処理能力をより有効に活用するため、この数字を上げてみてください。\n\nデフォルト値:ホストの CPU コア数です", - "xpack.main.welcomeBanner.licenseIsExpiredDescription": "管理者または {updateYourLicenseLinkText} に直接お問い合わせください。", - "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", - "xpack.main.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "データソースを変更", "xpack.maps.addLayerPanel.chooseDataSourceTitle": "データソースの選択", "xpack.maps.appDescription": "マップアプリケーション", @@ -12048,23 +12047,7 @@ "visTypeMarkdown.params.fontSizeLabel": "ポイント単位のベースフォントサイズです。", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "アクションタイプ \"{id}\" は登録されていません。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "アクションタイプ \"{id}\" は既に登録されています。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "アクション「{actionId}」メールの送信中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "アクション「{actionId}」データのインデックス中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "アクション {actionId} の構成で設定されていないため、インデックスパラメーターの設定が必要です", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: ステータス {status}、後程再試行してください", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: 予期せぬステータス {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "アクション「{actionId}」メッセージのログ記録宙にエラーが発生: {errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {retryString} に再試行してください: {message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました。後程再試行してください", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "Slack から予期せぬテキスト応答", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "無効な応答:Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。{retryString} で後程再試行してください: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。後程再試行してください", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "到達不能な Web フック: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "リモートウェブフックにアクセスできません。アドレスが正しいことを確認してください。", "xpack.actions.builtin.webhook.webhookConfigurationError": "Web フックアクションの構成中にエラーが発生: {message}", "xpack.actions.urlWhitelistConfigurationError": "ターゲット {field} 「{value}」は Kibana のホワイトリストに登録されていません", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "パネルに追加", @@ -12748,6 +12731,9 @@ "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licensing.check.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません。", "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", - "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" + "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription": "管理者または {updateYourLicenseLinkText} に直接お問い合わせください。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", + "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです" } } \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6de1bc2135351..974467a8d20d0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "连接数据", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "索引模式", + "management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", + "management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", + "management.editIndexPattern.createIndex.defaultTypeName": "索引模式", "common.ui.management.nav.menu": "管理菜单", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", @@ -677,6 +677,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "保存 {objectType}", "kibana-react.savedObjects.saveModal.titleLabel": "标题", + "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", + "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", "inspector.closeButton": "关闭检查器", "inspector.reqTimestampDescription": "记录请求启动的时间", "inspector.reqTimestampKey": "请求时间戳", @@ -6396,9 +6398,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "返回", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 参数必须包含 id 属性", "xpack.logstash.workersTooltip": "并行执行管道的筛选和输出阶段的工作线程数目。如果您发现事件出现积压或 CPU 未饱和,请考虑增大此数值,以更好地利用机器处理能力。\n\n默认值:主机的 CPU 核心数", - "xpack.main.welcomeBanner.licenseIsExpiredDescription": "联系您的管理员或直接{updateYourLicenseLinkText}。", - "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "更新您的许可", - "xpack.main.welcomeBanner.licenseIsExpiredTitle": "您的{licenseType}许可已过期", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "更改数据源", "xpack.maps.addLayerPanel.chooseDataSourceTitle": "选择数据源", "xpack.maps.appDescription": "地图应用程序", @@ -12137,23 +12136,7 @@ "visTypeMarkdown.params.fontSizeLabel": "基础字体大小(磅)", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "未注册操作类型“{id}”。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "操作类型“{id}”已注册。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "发送电子邮件时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "索引数据时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "需要设置索引参数,因为在操作 {actionId} 的配置中未设置", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:状态 {status},请稍后重试", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:异常错误 {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "记录消息时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "发布 slack 消息时操作“{id}”出现错误:{message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请稍后重试", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "来自 slack 的异常空响应", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "来自 slack 的异常文本响应", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请稍后重试", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "Webhook无法访问:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "远程 Webhook 无法访问,是否确定地址正确?", "xpack.actions.builtin.webhook.webhookConfigurationError": "配置 Webhook 操作时出错:{message}", "xpack.actions.urlWhitelistConfigurationError": "目标 {field}“{value}”不在 Kibana 白名单中", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "添加到面板", @@ -12837,6 +12820,9 @@ "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licensing.check.errorExpiredMessage": "您不能使用 {pluginName},因为您的{licenseType}许可证已过期。", "xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", - "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。" + "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription": "联系您的管理员或直接{updateYourLicenseLinkText}。", + "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "更新您的许可", + "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "您的{licenseType}许可已过期" } -} \ No newline at end of file +} diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 2ac8fff6ef8ab..f3e9db0053ad6 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -15,6 +15,7 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), require.resolve('../test/plugin_api_integration/config.js'), + require.resolve('../test/plugin_functional/config'), require.resolve('../test/kerberos_api_integration/config'), require.resolve('../test/kerberos_api_integration/anonymous_access.config'), require.resolve('../test/saml_api_integration/config'), @@ -34,4 +35,5 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/ui_capabilities/security_only/config'), require.resolve('../test/ui_capabilities/spaces_only/config'), require.resolve('../test/upgrade_assistant_integration/config'), + require.resolve('../test/licensing_plugin/config'), ]); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index b2e69a2121825..30b235a784c22 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -19,7 +19,7 @@ export default function(kibana: any) { id: 'test.noop', name: 'Test: Noop', async executor() { - return { status: 'ok' }; + return { status: 'ok', actionId: '' }; }, }; const indexRecordActionType: ActionType = { @@ -101,6 +101,7 @@ export default function(kibana: any) { return { status: 'error', retry: new Date(params.retryAt), + actionId: '', }; }, }; @@ -162,6 +163,7 @@ export default function(kibana: any) { }); return { status: 'ok', + actionId: '', }; }, }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index 197ad3ff80094..aacf0b8f87ed0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -234,7 +234,7 @@ export default function indexTest({ getService }: FtrProviderContext) { result = response.body; expect(result.status).to.equal('error'); expect(result.message).to.eql( - `index param needs to be set because not set in config for action ${createdActionID}` + 'index param needs to be set because not set in config for action' ); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index 62c8b31209729..54f38d2fd747e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -121,6 +121,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result).to.eql({ status: 'ok', + actionId: simulatedActionId, data: { dedup_key: `action:${simulatedActionId}`, message: 'Event processed', @@ -140,9 +141,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: unexpected status 418/ - ); + expect(result.message).to.match(/error posting pagerduty event: unexpected status 418/); }); it('should handle a 429 pagerduty error', async () => { @@ -158,7 +157,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(result.status).to.equal('error'); expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 429, retry later/ + /error posting pagerduty event: http status 429, retry later/ ); expect(result.retry).to.equal(true); }); @@ -175,9 +174,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 502, retry later/ - ); + expect(result.message).to.match(/error posting pagerduty event: http status 502/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index ae1464023c011..288eda5111a1e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -126,7 +126,7 @@ export default function slackTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/unexpected http response from slack: /); }); it('should handle a 429 slack error', async () => { @@ -142,8 +142,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); - expect(result.message).to.match(/retry at/); + expect(result.message).to.match(/error posting a slack message, retry at \d\d\d\d-/); const dateRetry = new Date(result.retry).getTime(); expect(dateRetry).to.greaterThan(dateStart); @@ -161,7 +160,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/error posting a slack message, retry later/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 53b11525d1b95..426bbe853ca19 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -196,7 +196,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Unreachable Remote Webhook/); + expect(result.message).to.match(/error calling webhook, unexpected error/); }); it('should handle failing webhook targets', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL); @@ -211,7 +211,8 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Bad Request/); + expect(result.message).to.match(/error calling webhook, invalid response/); + expect(result.serviceMessage).to.eql('[400] Bad Request'); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index edfb340bfd784..8f3996f958bb2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -532,6 +532,7 @@ export default function alertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'superuser at space1': + expect(response.statusCode).to.eql(200); // Wait for actions to execute twice before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2); await alertUtils.disable(response.body.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index bf61ee2e3f137..c0a99ae068e71 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -31,11 +31,32 @@ export default function createAlertTests({ getService }: FtrProviderContext) { const { user, space } = scenario; describe(scenario.id, () => { it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -56,7 +77,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 31af7a0acffbb..359058f2ac23a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -78,10 +78,32 @@ export default function createFindTests({ getService }: FtrProviderContext) { }); it('should handle find alert request with filter appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()) + .send( + getTestAlertData({ + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ) .expect(200); objectRemover.add(space.id, createdAlert.id, 'alert'); @@ -89,7 +111,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .get( `${getUrlPrefix( space.id - )}/api/alert/_find?filter=alert.attributes.alertTypeId:test.noop` + )}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -117,11 +139,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { tags: ['foo'], alertTypeId: 'test.noop', interval: '1m', - enabled: true, - actions: [], + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + actionTypeId: 'test.noop', + params: {}, + }, + ], params: {}, createdBy: 'elastic', - scheduledTaskId: match.scheduledTaskId, throttle: '1m', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 3018f8efffffe..929905a958abb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -27,10 +27,31 @@ export default function createAlertTests({ getService }: FtrProviderContext) { } it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); expect(response.statusCode).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); @@ -38,7 +59,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, diff --git a/x-pack/test/api_integration/apis/licensing/index.ts b/x-pack/test/api_integration/apis/endpoint/index.ts similarity index 63% rename from x-pack/test/api_integration/apis/licensing/index.ts rename to x-pack/test/api_integration/apis/endpoint/index.ts index f14d5102f6f4e..e0ffbb13e5978 100644 --- a/x-pack/test/api_integration/apis/licensing/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/index.ts @@ -6,8 +6,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function licensingIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('Licensing', () => { - loadTestFile(require.resolve('./info')); +export default function endpointAPIIntegrationTests({ loadTestFile }: FtrProviderContext) { + describe('Endpoint plugin', function() { + loadTestFile(require.resolve('./resolver')); }); } diff --git a/x-pack/test/api_integration/apis/endpoint/resolver.ts b/x-pack/test/api_integration/apis/endpoint/resolver.ts new file mode 100644 index 0000000000000..96d16e0d76e40 --- /dev/null +++ b/x-pack/test/api_integration/apis/endpoint/resolver.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const commonHeaders = { + Accept: 'application/json', + 'kbn-xsrf': 'some-xsrf-token', +}; + +// eslint-disable-next-line import/no-default-export +export default function resolverAPIIntegrationTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('Resolver api', function() { + it('should respond to hello-world', async function() { + const { body } = await supertest + .get('/api/endpoint/hello-world') + .set(commonHeaders) + .expect('Content-Type', /application\/json/) + .expect(200); + + expect(body).to.eql({ hello: 'world' }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index ca339e9f407f2..ed0e6488320d4 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -27,6 +27,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./siem')); loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); - loadTestFile(require.resolve('./licensing')); + loadTestFile(require.resolve('./endpoint')); }); } diff --git a/x-pack/test/api_integration/apis/infra/feature_controls.ts b/x-pack/test/api_integration/apis/infra/feature_controls.ts index 24d378d9b9a77..6556c309f31c5 100644 --- a/x-pack/test/api_integration/apis/infra/feature_controls.ts +++ b/x-pack/test/api_integration/apis/infra/feature_controls.ts @@ -19,7 +19,6 @@ const introspectionQuery = gql` `; export default function({ getService }: FtrProviderContext) { - const supertest = getService('supertestWithoutAuth'); const security = getService('security'); const spaces = getService('spaces'); const clientFactory = getService('infraOpsGraphQLClientFactory'); @@ -37,18 +36,6 @@ export default function({ getService }: FtrProviderContext) { expect(result.response.data).to.be.an('object'); }; - const expectGraphIQL404 = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 404); - }; - - const expectGraphIQLResponse = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 200); - }; - const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => { const queryOptions = { query: introspectionQuery, @@ -70,16 +57,6 @@ export default function({ getService }: FtrProviderContext) { }; }; - const executeGraphIQLRequest = async (username: string, password: string, spaceId?: string) => { - const basePath = spaceId ? `/s/${spaceId}` : ''; - - return supertest - .get(`${basePath}/api/infra/graphql/graphiql`) - .auth(username, password) - .then((response: any) => ({ error: undefined, response })) - .catch((error: any) => ({ error, response: undefined })); - }; - describe('feature controls', () => { it(`APIs can't be accessed by user with logstash-* "read" privileges`, async () => { const username = 'logstash_read'; @@ -105,9 +82,6 @@ export default function({ getService }: FtrProviderContext) { const graphQLResult = await executeGraphQLQuery(username, password); expectGraphQL404(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password); - expectGraphIQL404(graphQLIResult); } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -144,9 +118,6 @@ export default function({ getService }: FtrProviderContext) { const graphQLResult = await executeGraphQLQuery(username, password); expectGraphQLResponse(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password); - expectGraphIQLResponse(graphQLIResult); } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -186,9 +157,6 @@ export default function({ getService }: FtrProviderContext) { const graphQLResult = await executeGraphQLQuery(username, password); expectGraphQL404(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password); - expectGraphIQL404(graphQLIResult); } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -268,25 +236,16 @@ export default function({ getService }: FtrProviderContext) { it('user_1 can access APIs in space_1', async () => { const graphQLResult = await executeGraphQLQuery(username, password, space1Id); expectGraphQLResponse(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id); - expectGraphIQLResponse(graphQLIResult); }); it(`user_1 can access APIs in space_2`, async () => { const graphQLResult = await executeGraphQLQuery(username, password, space2Id); expectGraphQLResponse(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password, space2Id); - expectGraphIQLResponse(graphQLIResult); }); it(`user_1 can't access APIs in space_3`, async () => { const graphQLResult = await executeGraphQLQuery(username, password, space3Id); expectGraphQL404(graphQLResult); - - const graphQLIResult = await executeGraphIQLRequest(username, password, space3Id); - expectGraphIQL404(graphQLIResult); }); }); }); diff --git a/x-pack/test/api_integration/config.js b/x-pack/test/api_integration/config.js index 9c67dfe61b957..e5860fba80770 100644 --- a/x-pack/test/api_integration/config.js +++ b/x-pack/test/api_integration/config.js @@ -23,6 +23,7 @@ export async function getApiIntegrationConfig({ readConfigFile }) { ...xPackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.idleTimeout=3600000', // 1 hour '--optimize.enabled=false', + '--xpack.endpoint.enabled=true', ], }, esTestCluster: { diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index 58ee1668df701..2bbc39969370b 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,8 +13,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/45321 - describe.skip('graph', function() { + describe('graph', function() { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); @@ -114,6 +113,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const circlesText = nodes.map(({ label }) => label); expect(circlesText.length).to.equal(expectedNodes.length); circlesText.forEach(circleText => { + log.debug(`Looking for ${circleText}`); expect(expectedNodes.includes(circleText)).to.be(true); }); }); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index 9f4506ecccf7c..386914b735554 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -278,6 +278,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/ecommerce'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -464,6 +465,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation displays the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); }); @@ -709,6 +722,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning pre-fills the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, { diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index 935fbc0102149..d41d96e40e2be 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -75,6 +75,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/farequote'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -170,6 +171,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -306,6 +319,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index ff2275837ce2e..296af3179ce3e 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -89,6 +89,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/ecommerce'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -197,6 +198,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -344,6 +357,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts index 1983e98a0123d..f6cd7b40bc7b1 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts @@ -74,6 +74,7 @@ export default function({ getService }: FtrProviderContext) { this.tags(['smoke', 'mlqa']); before(async () => { await esArchiver.load('ml/farequote'); + await ml.api.createCalendar('wizard-test-calendar'); }); after(async () => { @@ -151,6 +152,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); }); + it('job creation opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job creation adds a new custom url', async () => { + await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); + }); + + it('job creation assigns calendars', async () => { + await ml.jobWizardCommon.addCalendar('wizard-test-calendar'); + }); + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); @@ -271,6 +284,18 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); }); + it('job cloning opens the additional settings section', async () => { + await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); + }); + + it('job cloning persists custom urls', async () => { + await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); + }); + + it('job cloning persists assigned calendars', async () => { + await ml.jobWizardCommon.assertCalendarsSelection(['wizard-test-calendar']); + }); + it('job cloning opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index f4b2be3420298..bf3435582ef47 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -153,10 +153,10 @@ export default async function ({ readConfigFile }) { pathname: '/app/uptime', }, apm: { - pathname: '/app/apm' + pathname: '/app/apm', }, ml: { - pathname: '/app/ml' + pathname: '/app/ml', }, rollupJob: { pathname: '/app/kibana', diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 1291e3dd10cff..a9d2601442aaa 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -411,7 +411,7 @@ "type": "envelope" }, "description": "", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", + "layerListJSON" : "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", "mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "join example", "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}" diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index 78863f91451f6..a4b4f500b8832 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -11,8 +11,22 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function CanvasPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); + const browser = getService('browser'); return { + async enterFullscreen() { + const elem = await find.byCssSelector('[aria-label="View fullscreen"]', 20000); + await elem.click(); + }, + + async exitFullscreen() { + await browser.pressKeys(browser.keys.ESCAPE); + }, + + async waitForWorkpadElements() { + await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); + }, + async expectCreateWorkpadButtonEnabled() { const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 780a541db0890..0d3e2c10579f5 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -235,6 +235,11 @@ export function GraphPageProvider({ getService, getPageObjects }: FtrProviderCon await this.goToListingPage(); await this.searchForWorkspaceWithName(name); await find.clickByLinkText(name); + // wait for nodes to show up + if (!(await find.existsByCssSelector('.gphNode', 10000))) { + throw new Error('nodes did not show up'); + } + // let force simulation settle down before continuing await PageObjects.common.sleep(5000); } diff --git a/x-pack/test/functional/page_objects/upgrade_assistant.js b/x-pack/test/functional/page_objects/upgrade_assistant.js index c6977b2150840..fc6faed170c9f 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant.js +++ b/x-pack/test/functional/page_objects/upgrade_assistant.js @@ -45,7 +45,7 @@ export function UpgradeAssistantProvider({ getService, getPageObjects }) { async expectDeprecationLoggingLabel(labelText) { return await retry.try(async () => { log.debug('expectDeprecationLoggingLabel()'); - const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ p'); + const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span'); const value = await label.getVisibleText(); expect(value).to.equal(labelText); }); diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index be65896950cfc..1995f37782948 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -247,5 +247,25 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { } }); }, + + async getCalendar(calendarId: string) { + return await esSupertest.get(`/_ml/calendars/${calendarId}`).expect(200); + }, + + async createCalendar(calendarId: string, body = { description: '', job_ids: [] }) { + log.debug(`Creating calendar with id '${calendarId}'...`); + await esSupertest + .put(`/_ml/calendars/${calendarId}`) + .send(body) + .expect(200); + + await retry.waitForWithTimeout(`'${calendarId}' to be created`, 30 * 1000, async () => { + if (await this.getCalendar(calendarId)) { + return true; + } else { + throw new Error(`expected calendar '${calendarId}' to be created`); + } + }); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/common.ts b/x-pack/test/functional/services/machine_learning/common.ts new file mode 100644 index 0000000000000..12b9e8a1cfb29 --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/common.ts @@ -0,0 +1,73 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +interface SetValueOptions { + clearWithKeyboard?: boolean; + typeCharByChar?: boolean; +} + +export function MachineLearningCommonProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + + return { + async setValueWithChecks( + selector: string, + text: string, + options: SetValueOptions = {} + ): Promise { + return await retry.try(async () => { + const { clearWithKeyboard = false, typeCharByChar = false } = options; + log.debug(`TestSubjects.setValueWithChecks(${selector}, ${text})`); + await testSubjects.click(selector); + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await find.activeElement(); + + await retry.tryForTime(5000, async () => { + let currentValue = await input.getAttribute('value'); + if (currentValue !== '') { + if (clearWithKeyboard === true) { + await input.clearValueWithKeyboard(); + } else { + await input.clearValue(); + } + currentValue = await input.getAttribute('value'); + } + + if (currentValue === '') { + return true; + } else { + throw new Error(`Expected input to be empty, but got value '${currentValue}'`); + } + }); + + for (const chr of text) { + await retry.tryForTime(5000, async () => { + const oldValue = await input.getAttribute('value'); + await input.type(chr, { charByChar: typeCharByChar }); + + await retry.tryForTime(1000, async () => { + const newValue = await input.getAttribute('value'); + if (newValue === `${oldValue}${chr}`) { + return true; + } else { + throw new Error( + `After typing character '${chr}', the new value in the input should be '${oldValue}${chr}' (got ${newValue})` + ); + } + }); + }); + } + }); + }, + }; +} diff --git a/x-pack/test/functional/services/machine_learning/custom_urls.ts b/x-pack/test/functional/services/machine_learning/custom_urls.ts new file mode 100644 index 0000000000000..dc6e4a2fccb10 --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/custom_urls.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function MachineLearningCustomUrlsProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertCustomUrlLabelValue(expectedValue: string) { + const actualCustomUrlLabel = await testSubjects.getAttribute( + 'mlJobCustomUrlLabelInput', + 'value' + ); + expect(actualCustomUrlLabel).to.eql(expectedValue); + }, + + async setCustomUrlLabel(customUrlsLabel: string) { + await testSubjects.setValue('mlJobCustomUrlLabelInput', customUrlsLabel, { + clearWithKeyboard: true, + }); + await this.assertCustomUrlLabelValue(customUrlsLabel); + }, + + async assertCustomUrlItem(index: number, label: string) { + await testSubjects.existOrFail(`mlJobEditCustomUrlItem_${index}`); + expect( + await testSubjects.getAttribute(`mlJobEditCustomUrlLabelInput_${index}`, 'value') + ).to.eql(label); + }, + + /** + * Submits the custom url form and adds it to the list. + * @param formContainerSelector - selector for the element that wraps the custom url creation form. + */ + async saveCustomUrl(formContainerSelector: string) { + await testSubjects.click('mlJobAddCustomUrl'); + await testSubjects.missingOrFail(formContainerSelector, { timeout: 10 * 1000 }); + }, + }; +} diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index c62b714f566e9..7c393689d166c 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -6,6 +6,8 @@ export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer'; export { MachineLearningAPIProvider } from './api'; +export { MachineLearningCommonProvider } from './common'; +export { MachineLearningCustomUrlsProvider } from './custom_urls'; export { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; export { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; export { MachineLearningDataFrameAnalyticsTableProvider } from './data_frame_analytics_table'; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 27768f784b1c5..ab53b0412ca35 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCommonProvider } from './common'; -export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobWizardAdvancedProvider( + { getService }: FtrProviderContext, + mlCommon: ProvidedType +) { const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -44,7 +49,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setQueryDelay(queryDelay: string) { - await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, { + await mlCommon.setValueWithChecks('mlJobWizardInputQueryDelay', queryDelay, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -61,7 +66,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setFrequency(frequency: string) { - await testSubjects.setValue('mlJobWizardInputFrequency', frequency, { + await mlCommon.setValueWithChecks('mlJobWizardInputFrequency', frequency, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -78,7 +83,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setScrollSize(scrollSize: string) { - await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, { + await mlCommon.setValueWithChecks('mlJobWizardInputScrollSize', scrollSize, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -257,7 +262,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setDetectorDescription(description: string) { - await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, { + await mlCommon.setValueWithChecks('mlAdvancedDetectorDescriptionInput', description, { clearWithKeyboard: true, }); await this.assertDetectorDescriptionValue(description); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 0ebc4cb959412..b9e6822c8f41a 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -4,10 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; - -export function MachineLearningJobWizardCommonProvider({ getService }: FtrProviderContext) { +import { MachineLearningCommonProvider } from './common'; +import { MachineLearningCustomUrlsProvider } from './custom_urls'; + +export function MachineLearningJobWizardCommonProvider( + { getService }: FtrProviderContext, + mlCommon: ProvidedType, + customUrls: ProvidedType +) { const comboBox = getService('comboBox'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -108,7 +115,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }, async setBucketSpan(bucketSpan: string) { - await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan, { + await mlCommon.setValueWithChecks('mlJobWizardInputBucketSpan', bucketSpan, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -125,7 +132,9 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }, async setJobId(jobId: string) { - await testSubjects.setValue('mlJobWizardInputJobId', jobId, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks('mlJobWizardInputJobId', jobId, { + clearWithKeyboard: true, + }); await this.assertJobIdValue(jobId); }, @@ -141,7 +150,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid }, async setJobDescription(jobDescription: string) { - await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription, { + await mlCommon.setValueWithChecks('mlJobWizardInputJobDescription', jobDescription, { clearWithKeyboard: true, }); await this.assertJobDescriptionValue(jobDescription); @@ -166,6 +175,23 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid expect(await this.getSelectedJobGroups()).to.contain(jobGroup); }, + async getSelectedCalendars(): Promise { + await this.ensureAdditionalSettingsSectionOpen(); + return await comboBox.getComboBoxSelectedOptions( + 'mlJobWizardComboBoxCalendars > comboBoxInput' + ); + }, + + async assertCalendarsSelection(calendars: string[]) { + expect(await this.getSelectedCalendars()).to.eql(calendars); + }, + + async addCalendar(calendarId: string) { + await this.ensureAdditionalSettingsSectionOpen(); + await comboBox.setCustom('mlJobWizardComboBoxCalendars > comboBoxInput', calendarId); + expect(await this.getSelectedCalendars()).to.contain(calendarId); + }, + async assertModelPlotSwitchExists( sectionOptions: SectionOptions = { withAdvancedSection: true } ) { @@ -285,7 +311,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await this.ensureAdvancedSectionOpen(); subj = advancedSectionSelector(subj); } - await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); await this.assertModelMemoryLimitValue(modelMemoryLimit, { withAdvancedSection: sectionOptions.withAdvancedSection, }); @@ -358,6 +384,40 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await this.assertDateRangeSelection(expectedStartDate, expectedEndDate); }, + async ensureAdditionalSettingsSectionOpen() { + await retry.tryForTime(5000, async () => { + if ((await testSubjects.exists('mlJobWizardAdditionalSettingsSection')) === false) { + await testSubjects.click('mlJobWizardToggleAdditionalSettingsSection'); + await testSubjects.existOrFail('mlJobWizardAdditionalSettingsSection', { timeout: 1000 }); + } + }); + }, + + async ensureNewCustomUrlFormModalOpen() { + await retry.tryForTime(5000, async () => { + if ((await testSubjects.exists('mlJobNewCustomUrlFormModal')) === false) { + await testSubjects.click('mlJobOpenCustomUrlFormButton'); + await testSubjects.existOrFail('mlJobNewCustomUrlFormModal', { timeout: 1000 }); + } + }); + }, + + async addCustomUrl(customUrl: { label: string }) { + await this.ensureAdditionalSettingsSectionOpen(); + + const existingCustomUrls = await testSubjects.findAll('mlJobEditCustomUrlsList > *'); + + await this.ensureNewCustomUrlFormModalOpen(); + // Fill-in the form + await customUrls.setCustomUrlLabel(customUrl.label); + // Save custom URL + await customUrls.saveCustomUrl('mlJobNewCustomUrlFormModal'); + + const expectedIndex = existingCustomUrls.length; + + await customUrls.assertCustomUrlItem(expectedIndex, customUrl.label); + }, + async ensureAdvancedSectionOpen() { await retry.tryForTime(5000, async () => { if ((await testSubjects.exists(advancedSectionSelector())) === false) { diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 86967dfd1e273..8e71c16921078 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -9,6 +9,8 @@ import { FtrProviderContext } from '../ftr_provider_context'; import { MachineLearningAnomalyExplorerProvider, MachineLearningAPIProvider, + MachineLearningCommonProvider, + MachineLearningCustomUrlsProvider, MachineLearningDataFrameAnalyticsProvider, MachineLearningDataFrameAnalyticsCreationProvider, MachineLearningDataFrameAnalyticsTableProvider, @@ -28,8 +30,11 @@ import { } from './machine_learning'; export function MachineLearningProvider(context: FtrProviderContext) { + const common = MachineLearningCommonProvider(context); + const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); const api = MachineLearningAPIProvider(context); + const customUrls = MachineLearningCustomUrlsProvider(context); const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context, api); const dataFrameAnalyticsCreation = MachineLearningDataFrameAnalyticsCreationProvider(context); const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context); @@ -39,8 +44,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); - const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context); - const jobWizardCommon = MachineLearningJobWizardCommonProvider(context); + const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, common); + const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, common, customUrls); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); const navigation = MachineLearningNavigationProvider(context); @@ -50,6 +55,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { return { anomalyExplorer, api, + customUrls, dataFrameAnalytics, dataFrameAnalyticsCreation, dataFrameAnalyticsTable, diff --git a/x-pack/test/licensing_plugin/apis/changes.ts b/x-pack/test/licensing_plugin/apis/changes.ts new file mode 100644 index 0000000000000..cbff783a0633c --- /dev/null +++ b/x-pack/test/licensing_plugin/apis/changes.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; +import { PublicLicenseJSON } from '../../../plugins/licensing/server'; + +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const supertest = getService('supertest'); + const esSupertestWithoutAuth = getService('esSupertestWithoutAuth'); + const security = getService('security'); + const PageObjects = getPageObjects(['common', 'security']); + const testSubjects = getService('testSubjects'); + + const scenario = { + async setup() { + await security.role.create('license_manager-role', { + elasticsearch: { + cluster: ['all'], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + await security.user.create('license_manager_user', { + password: 'license_manager_user-password', + roles: ['license_manager-role'], + full_name: 'license_manager user', + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + await PageObjects.security.login('license_manager_user', 'license_manager_user-password'); + }, + + async teardown() { + await security.role.delete('license_manager-role'); + }, + + async startBasic() { + const response = await esSupertestWithoutAuth + .post('/_license/start_basic?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.basic_was_started).to.be(true); + }, + + async startTrial() { + const response = await esSupertestWithoutAuth + .post('/_license/start_trial?acknowledge=true') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.trial_was_started).to.be(true); + }, + + async deleteLicense() { + const response = await esSupertestWithoutAuth + .delete('/_license') + .auth('license_manager_user', 'license_manager_user-password') + .expect(200); + + expect(response.body.acknowledged).to.be(true); + }, + + async getLicense(): Promise { + // > --xpack.licensing.pollingFrequency set in test config + // to wait for Kibana server to re-fetch the license from Elasticsearch + await delay(1000); + + const { body } = await supertest.get('/api/licensing/info').expect(200); + return body; + }, + }; + + describe('changes in license types', () => { + after(async () => { + await scenario.startBasic(); + }); + + it('provides changes in license types', async () => { + await scenario.setup(); + const initialLicense = await scenario.getLicense(); + expect(initialLicense.license?.type).to.be('basic'); + // security enabled explicitly in test config + expect(initialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + const refetchedLicense = await scenario.getLicense(); + expect(refetchedLicense.license?.type).to.be('basic'); + expect(refetchedLicense.signature).to.be(initialLicense.signature); + + // server allows to request trial only once. + // other attempts will throw 403 + await scenario.startTrial(); + const trialLicense = await scenario.getLicense(); + expect(trialLicense.license?.type).to.be('trial'); + expect(trialLicense.signature).to.not.be(initialLicense.signature); + expect(trialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.startBasic(); + const basicLicense = await scenario.getLicense(); + expect(basicLicense.license?.type).to.be('basic'); + expect(basicLicense.signature).not.to.be(initialLicense.signature); + expect(trialLicense.features?.security).to.eql({ + isAvailable: true, + isEnabled: true, + }); + + await scenario.deleteLicense(); + const inactiveLicense = await scenario.getLicense(); + expect(inactiveLicense.signature).to.not.be(initialLicense.signature); + expect(inactiveLicense).to.not.have.property('license'); + expect(inactiveLicense.features?.security).to.eql({ + isAvailable: false, + isEnabled: true, + }); + // banner shown only when license expired not just deleted + await testSubjects.missingOrFail('licenseExpiredBanner'); + }); + }); +} diff --git a/x-pack/test/licensing_plugin/apis/header.ts b/x-pack/test/licensing_plugin/apis/header.ts new file mode 100644 index 0000000000000..8d95054feaaf2 --- /dev/null +++ b/x-pack/test/licensing_plugin/apis/header.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../services'; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('Header', () => { + it("Injects 'kbn-license-sig' header to the all responses", async () => { + const response = await supertest.get('/'); + + expect(response.header).property('kbn-license-sig'); + expect(response.header['kbn-license-sig']).to.not.be.empty(); + }); + }); +} diff --git a/x-pack/test/licensing_plugin/apis/index.ts b/x-pack/test/licensing_plugin/apis/index.ts new file mode 100644 index 0000000000000..fbc0449dcd8fc --- /dev/null +++ b/x-pack/test/licensing_plugin/apis/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../services'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('Licensing plugin', function() { + this.tags('ciGroup2'); + loadTestFile(require.resolve('./info')); + loadTestFile(require.resolve('./header')); + + // MUST BE LAST! CHANGES LICENSE TYPE! + loadTestFile(require.resolve('./changes')); + }); +} diff --git a/x-pack/test/api_integration/apis/licensing/info.ts b/x-pack/test/licensing_plugin/apis/info.ts similarity index 75% rename from x-pack/test/api_integration/apis/licensing/info.ts rename to x-pack/test/licensing_plugin/apis/info.ts index 0b48080616fb9..7ec009d85cd09 100644 --- a/x-pack/test/api_integration/apis/licensing/info.ts +++ b/x-pack/test/licensing_plugin/apis/info.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../services'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -19,6 +19,12 @@ export default function({ getService }: FtrProviderContext) { expect(response.body).property('license'); expect(response.body).property('signature'); }); + + it('returns a correct license type', async () => { + const response = await supertest.get('/api/licensing/info').expect(200); + + expect(response.body.license.type).to.be('basic'); + }); }); }); } diff --git a/x-pack/test/licensing_plugin/config.ts b/x-pack/test/licensing_plugin/config.ts new file mode 100644 index 0000000000000..810dd3edc76b9 --- /dev/null +++ b/x-pack/test/licensing_plugin/config.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services, pageObjects } from './services'; + +const license = 'basic'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const functionalTestsConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const servers = { + ...functionalTestsConfig.get('servers'), + elasticsearch: { + ...functionalTestsConfig.get('servers.elasticsearch'), + }, + kibana: { + ...functionalTestsConfig.get('servers.kibana'), + }, + }; + + return { + testFiles: [require.resolve('./apis')], + servers, + services, + pageObjects, + junit: { + reportName: 'License plugin API Integration Tests', + }, + + esTestCluster: { + ...functionalTestsConfig.get('esTestCluster'), + license, + serverArgs: [ + ...functionalTestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.enabled=true', + ], + }, + + kbnTestServer: { + ...functionalTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalTestsConfig.get('kbnTestServer.serverArgs'), + '--xpack.licensing.pollingFrequency=300', + ], + }, + + apps: { + ...functionalTestsConfig.get('apps'), + }, + }; +} diff --git a/x-pack/test/licensing_plugin/services.ts b/x-pack/test/licensing_plugin/services.ts new file mode 100644 index 0000000000000..7ded6df80cf55 --- /dev/null +++ b/x-pack/test/licensing_plugin/services.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { services as functionalTestServices } from '../functional/services'; +import { services as kibanaApiIntegrationServices } from '../api_integration/services'; +import { pageObjects } from '../functional/page_objects'; + +export const services = { + ...functionalTestServices, + supertest: kibanaApiIntegrationServices.supertest, + esSupertestWithoutAuth: kibanaApiIntegrationServices.esSupertestWithoutAuth, +}; + +export { pageObjects }; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/plugin_functional/config.ts b/x-pack/test/plugin_functional/config.ts new file mode 100644 index 0000000000000..6c3c496da71f6 --- /dev/null +++ b/x-pack/test/plugin_functional/config.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { resolve } from 'path'; +import fs from 'fs'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; +import { pageObjects } from './page_objects'; + +// the default export of config files must be a config provider +// that returns an object with the projects config values + +/* eslint-disable import/no-default-export */ +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + // Find all folders in ./plugins since we treat all them as plugin folder + const allFiles = fs.readdirSync(resolve(__dirname, 'plugins')); + const plugins = allFiles.filter(file => + fs.statSync(resolve(__dirname, 'plugins', file)).isDirectory() + ); + + return { + // list paths to the files that contain your plugins tests + testFiles: [resolve(__dirname, './test_suites/resolver')], + + services, + pageObjects, + + servers: xpackFunctionalConfig.get('servers'), + + esTestCluster: xpackFunctionalConfig.get('esTestCluster'), + + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + ...plugins.map(pluginDir => `--plugin-path=${resolve(__dirname, 'plugins', pluginDir)}`), + // Required to load new platform plugins via `--plugin-path` flag. + '--env.name=development', + '--xpack.endpoint.enabled=true', + ], + }, + uiSettings: xpackFunctionalConfig.get('uiSettings'), + // the apps section defines the urls that + // `PageObjects.common.navigateTo(appKey)` will use. + // Merge urls for your plugin with the urls defined in + // Kibana's config in order to use this helper + apps: { + ...xpackFunctionalConfig.get('apps'), + resolverTest: { + pathname: '/app/resolver_test', + }, + }, + + // choose where esArchiver should load archives from + esArchiver: { + directory: resolve(__dirname, 'es_archives'), + }, + + // choose where screenshots should be saved + screenshots: { + directory: resolve(__dirname, 'screenshots'), + }, + + junit: { + reportName: 'Chrome X-Pack UI Plugin Functional Tests', + }, + }; +} diff --git a/x-pack/test/plugin_functional/ftr_provider_context.d.ts b/x-pack/test/plugin_functional/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..271f313d4bda9 --- /dev/null +++ b/x-pack/test/plugin_functional/ftr_provider_context.d.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; +import { pageObjects } from './page_objects'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/plugin_functional/page_objects.ts b/x-pack/test/plugin_functional/page_objects.ts new file mode 100644 index 0000000000000..a216b0f2cd47a --- /dev/null +++ b/x-pack/test/plugin_functional/page_objects.ts @@ -0,0 +1,6 @@ +/* + * 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 { pageObjects } from '../functional/page_objects'; diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/kibana.json b/x-pack/test/plugin_functional/plugins/resolver_test/kibana.json new file mode 100644 index 0000000000000..c715a0aaa3b20 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/resolver_test/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "resolver_test", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "resolver_test"], + "requiredPlugins": ["embeddable"], + "server": false, + "ui": true +} diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx new file mode 100644 index 0000000000000..98baad6a18411 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx @@ -0,0 +1,63 @@ +/* + * 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 React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters } from 'kibana/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import { IEmbeddable } from 'src/plugins/embeddable/public'; +import { useEffect } from 'react'; + +/** + * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. + */ +export function renderApp( + { element }: AppMountParameters, + embeddable: Promise +) { + ReactDOM.render( + + + , + element + ); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +} + +const AppRoot = React.memo( + ({ embeddable: embeddablePromise }: { embeddable: Promise }) => { + const [embeddable, setEmbeddable] = React.useState(undefined); + const [renderTarget, setRenderTarget] = React.useState(null); + + useEffect(() => { + let cleanUp; + Promise.race([ + new Promise((_resolve, reject) => { + cleanUp = reject; + }), + embeddablePromise, + ]).then(value => { + setEmbeddable(value); + }); + + return cleanUp; + }, [embeddablePromise]); + + useEffect(() => { + if (embeddable && renderTarget) { + embeddable.render(renderTarget); + return () => { + embeddable.destroy(); + }; + } + }, [embeddable, renderTarget]); + + return
; + } +); diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/index.ts b/x-pack/test/plugin_functional/plugins/resolver_test/public/index.ts new file mode 100644 index 0000000000000..c5f3c0e19138f --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { ResolverTestPlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new ResolverTestPlugin(); diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts b/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts new file mode 100644 index 0000000000000..f063271f4b5dd --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts @@ -0,0 +1,53 @@ +/* + * 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 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { IEmbeddable, IEmbeddableStart } from '../../../../../../src/plugins/embeddable/public'; + +export type ResolverTestPluginSetup = void; +export type ResolverTestPluginStart = void; +export interface ResolverTestPluginSetupDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface ResolverTestPluginStartDependencies { + embeddable: IEmbeddableStart; +} + +export class ResolverTestPlugin + implements + Plugin< + ResolverTestPluginSetup, + ResolverTestPluginStart, + ResolverTestPluginSetupDependencies, + ResolverTestPluginStartDependencies + > { + private resolveEmbeddable!: ( + value: IEmbeddable | undefined | PromiseLike | undefined + ) => void; + private embeddablePromise: Promise = new Promise< + IEmbeddable | undefined + >(resolve => { + this.resolveEmbeddable = resolve; + }); + public setup(core: CoreSetup) { + core.application.register({ + id: 'resolver_test', + title: i18n.translate('xpack.resolver_test.pluginTitle', { + defaultMessage: 'Resolver Test', + }), + mount: async (_context, params) => { + const { renderApp } = await import('./applications/resolver_test'); + return renderApp(params, this.embeddablePromise); + }, + }); + } + + public start(...args: [unknown, { embeddable: IEmbeddableStart }]) { + const [, plugins] = args; + const factory = plugins.embeddable.getEmbeddableFactory('resolver'); + this.resolveEmbeddable(factory.create({ id: 'test basic render' })); + } + public stop() {} +} diff --git a/x-pack/legacy/plugins/license_management/common/constants/plugin.js b/x-pack/test/plugin_functional/services.ts similarity index 81% rename from x-pack/legacy/plugins/license_management/common/constants/plugin.js rename to x-pack/test/plugin_functional/services.ts index bab1345a33e7d..5c807720b2867 100644 --- a/x-pack/legacy/plugins/license_management/common/constants/plugin.js +++ b/x-pack/test/plugin_functional/services.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN = { - ID: 'license_management', -}; +export { services } from '../functional/services'; diff --git a/x-pack/test/plugin_functional/test_suites/resolver/index.ts b/x-pack/test/plugin_functional/test_suites/resolver/index.ts new file mode 100644 index 0000000000000..a0735f216e309 --- /dev/null +++ b/x-pack/test/plugin_functional/test_suites/resolver/index.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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + describe('Resolver embeddable test app', function() { + this.tags('ciGroup7'); + + beforeEach(async function() { + await pageObjects.common.navigateToApp('resolverTest'); + }); + + it('renders a container div for the embeddable', async function() { + await testSubjects.existOrFail('resolverEmbeddableContainer'); + }); + it('renders resolver', async function() { + await testSubjects.existOrFail('resolverEmbeddable'); + }); + }); +} diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 0c528a80b4334..b8a0789f1bff3 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -21,7 +21,9 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['reporting', 'common', 'dashboard', 'header', 'discover', 'visualize']); const log = getService('log'); - describe('Reporting', () => { + // FLAKY: https://github.com/elastic/kibana/issues/45499 + // FLAKY: https://github.com/elastic/kibana/issues/48721 + describe.skip('Reporting', () => { before('initialize tests', async () => { await PageObjects.reporting.initTests(); @@ -73,8 +75,7 @@ export default function ({ getService, getPageObjects }) { await expectDisabledGenerateReportButton(); }); - // FLAKY: https://github.com/elastic/kibana/issues/45499 - it.skip('becomes available when saved', async () => { + it('becomes available when saved', async () => { await PageObjects.dashboard.saveDashboard('mypdfdash'); await PageObjects.reporting.openPdfReportingPanel(); await expectEnabledGenerateReportButton(); diff --git a/x-pack/test/typings/rison_node.d.ts b/x-pack/test/typings/rison_node.d.ts deleted file mode 100644 index ec8e5c1f407ad..0000000000000 --- a/x-pack/test/typings/rison_node.d.ts +++ /dev/null @@ -1,26 +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. - */ - -declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface RisonArray extends Array {} - - export interface RisonObject { - [key: string]: RisonValue; - } - - export const decode: (input: string) => RisonValue; - - // eslint-disable-next-line @typescript-eslint/camelcase - export const decode_object: (input: string) => RisonObject; - - export const encode: (input: Input) => string; - - // eslint-disable-next-line @typescript-eslint/camelcase - export const encode_object: (input: Input) => string; -} diff --git a/x-pack/test/visual_regression/config.js b/x-pack/test/visual_regression/config.js index 4f77098d6a456..ad9a714ee8d02 100644 --- a/x-pack/test/visual_regression/config.js +++ b/x-pack/test/visual_regression/config.js @@ -13,6 +13,7 @@ export default async function ({ readConfigFile }) { ...functionalConfig.getAll(), testFiles: [ + require.resolve('./tests/canvas'), require.resolve('./tests/login_page'), require.resolve('./tests/maps'), require.resolve('./tests/infra'), diff --git a/x-pack/test/visual_regression/tests/canvas/fullscreen.js b/x-pack/test/visual_regression/tests/canvas/fullscreen.js new file mode 100644 index 0000000000000..998fbb2c21f40 --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/fullscreen.js @@ -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. +*/ + +export default function ({ getPageObjects, getService }) { + const PageObjects = getPageObjects(['common', 'canvas']); + const visualTesting = getService('visualTesting'); + + describe('fullscreen', () => { + it('workpad should display properly in fullscreen mode', async () => { + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1' + }); + + await PageObjects.canvas.enterFullscreen(); + + await PageObjects.canvas.waitForWorkpadElements(); + + await visualTesting.snapshot(); + }); + }); +} diff --git a/x-pack/test/visual_regression/tests/canvas/index.js b/x-pack/test/visual_regression/tests/canvas/index.js new file mode 100644 index 0000000000000..29bfd07c525cd --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/index.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 { DEFAULT_OPTIONS } from '../../../../../test/visual_regression/services/visual_testing/visual_testing'; + +const [SCREEN_WIDTH] = DEFAULT_OPTIONS.widths || []; + +export default function ({ loadTestFile, getService }) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('canvas app visual regression', function () { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('canvas/default'); + + await browser.setWindowSize(SCREEN_WIDTH, 1000); + }); + + after(async () => { + await esArchiver.unload('canvas/default'); + }); + + this.tags('ciGroup10'); + loadTestFile(require.resolve('./fullscreen')); + }); +} diff --git a/yarn.lock b/yarn.lock index 49216f9dac056..cfef1bec7e6a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,10 +1226,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@16.1.0": - version "16.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-16.1.0.tgz#b8311a4dd3146da2fcd8cec6ae1ed46e26f90746" - integrity sha512-WbfIc2RGojrUeYyEIjiQ0Cy4xbk6YJ6eQ+ymFHSDGqwkKy5IidSBZIDNLM13LzAhxBFTAMmfVsk4lB2qiFlolQ== +"@elastic/eui@17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-17.0.0.tgz#2b53500b9f4849fdf58f88ea89e10a0b58836acc" + integrity sha512-bWTVKbChPGh7AHLxW1o9yp7O3hL9lTVj69DVaaLBOz9K0JnDrWZ5VDvWL9AAguFWq9OEMY9oWjyBhQZr1ZeZbA== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -4238,24 +4238,24 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.9.0.tgz#fa810282c0e45f6c2310b9c0dfcd25bff97ab7e9" - integrity sha512-98rfOt3NYn5Gr9wekTB8TexxN6oM8ZRvYuphPs1Atfsy419SDLYCaE30aJkRiiTCwGEY98vOhFsEVm7Zs4toQQ== +"@typescript-eslint/eslint-plugin@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.10.0.tgz#c4cb103275e555e8a7e9b3d14c5951eb6d431e70" + integrity sha512-rT51fNLW0u3fnDGnAHVC5nu+Das+y2CpW10yqvf6/j5xbuUV3FxA3mBaIbM24CXODXjbgUznNb4Kg9XZOUxKAw== dependencies: - "@typescript-eslint/experimental-utils" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.9.0.tgz#bbe99a8d9510240c055fc4b17230dd0192ba3c7f" - integrity sha512-0lOLFdpdJsCMqMSZT7l7W2ta0+GX8A3iefG3FovJjrX+QR8y6htFlFdU7aOVPL6pDvt6XcsOb8fxk5sq+girTw== +"@typescript-eslint/experimental-utils@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.10.0.tgz#8db1656cdfd3d9dcbdbf360b8274dea76f0b2c2c" + integrity sha512-FZhWq6hWWZBP76aZ7bkrfzTMP31CCefVIImrwP3giPLcoXocmLTmr92NLZxuIcTL4GTEOE33jQMWy9PwelL+yQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-scope "^5.0.0" "@typescript-eslint/experimental-utils@^1.13.0": @@ -4267,14 +4267,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/parser@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.9.0.tgz#2e9cf438de119b143f642a3a406e1e27eb70b7cd" - integrity sha512-fJ+dNs3CCvEsJK2/Vg5c2ZjuQ860ySOAsodDPwBaVlrGvRN+iCNC8kUfLFL8cT49W4GSiLPa/bHiMjYXA7EhKQ== +"@typescript-eslint/parser@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.10.0.tgz#24b2e48384ab6d5a6121e4c4faf8892c79657ad3" + integrity sha512-wQNiBokcP5ZsTuB+i4BlmVWq6o+oAhd8en2eSm/EE9m7BgZUIfEeYFd6z3S+T7bgNuloeiHA1/cevvbBDLr98g== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.9.0" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -4285,10 +4285,10 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.9.0.tgz#09138daf8f47d0e494ba7db9e77394e928803017" - integrity sha512-v6btSPXEWCP594eZbM+JCXuFoXWXyF/z8kaSBSdCb83DF+Y7+xItW29SsKtSULgLemqJBT+LpT+0ZqdfH7QVmA== +"@typescript-eslint/typescript-estree@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.10.0.tgz#89cdabd5e8c774e9d590588cb42fb9afd14dcbd9" + integrity sha512-oOYnplddQNm/LGVkqbkAwx4TIBuuZ36cAQq9v3nFIU9FmhemHuVzAesMSXNQDdAzCa5bFgCrfD3JWhYVKlRN2g== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -6809,6 +6809,11 @@ bounce@1.x.x: boom "7.x.x" hoek "5.x.x" +bowser@^1.7.3: + version "1.9.4" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" + integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== + boxen@^1.2.1, boxen@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -8719,6 +8724,13 @@ copy-to-clipboard@^3.0.8: dependencies: toggle-selection "^1.0.3" +copy-to-clipboard@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467" + integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w== + dependencies: + toggle-selection "^1.0.6" + copy-webpack-plugin@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655" @@ -9096,6 +9108,14 @@ css-color-keywords@^1.0.0: resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= +css-in-js-utils@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99" + integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA== + dependencies: + hyphenate-style-name "^1.0.2" + isobject "^3.0.1" + css-loader@2.1.1, css-loader@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea" @@ -9181,6 +9201,14 @@ css-tree@1.0.0-alpha.29: mdn-data "~1.1.0" source-map "^0.5.3" +css-tree@^1.0.0-alpha.28: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + css-url-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" @@ -9252,7 +9280,7 @@ cssstyle@^2.0.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7: +csstype@^2.2.0, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ== @@ -10998,6 +11026,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.4.tgz#a757397dc5d9de973ac9a5d7d4e8ade7cfae9101" + integrity sha512-fZ0KkoxSjLFmhW5lHbUT3tLwy3nX1qEzMYo8koY1vrsAco53CMT1djnBSeC/wUjTEZRhZl9iRw7PaMaxfJ4wzQ== + dependencies: + stackframe "^1.1.0" + error@^7.0.0, error@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" @@ -12008,12 +12043,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= - -extend@~3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.0, extend@~3.0.1, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -12174,6 +12204,11 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" @@ -12240,6 +12275,11 @@ fast-stream-to-buffer@^1.0.0: dependencies: end-of-stream "^1.4.1" +fastest-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028" + integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg= + fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -15158,6 +15198,11 @@ hyperlinker@^1.0.0: resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== +hyphenate-style-name@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" + integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== + i18n-iso-countries@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-4.3.1.tgz#f110a8824ce14edbb0eb8f3b0bd817ff950af37c" @@ -15437,6 +15482,14 @@ ini@^1.2.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +inline-style-prefixer@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911" + integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg== + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inline-style@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/inline-style/-/inline-style-2.0.0.tgz#2fa9cf624596a8109355b925094e138bbd5ea29b" @@ -18958,6 +19011,11 @@ mdast-add-list-metadata@1.0.1: dependencies: unist-util-visit-parents "1.1.2" +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== + mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" @@ -19828,6 +19886,20 @@ nan@^2.10.0, nan@^2.9.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA== +nano-css@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.2.1.tgz#73b8470fa40b028a134d3393ae36bbb34b9fa332" + integrity sha512-T54okxMAha0+de+W8o3qFtuWhTxYvqQh2ku1cYEqTTP9mR62nWV2lLK9qRuAGWmoaYWhU7K4evT9Lc1iF65wuw== + dependencies: + css-tree "^1.0.0-alpha.28" + csstype "^2.5.5" + fastest-stable-stringify "^1.0.1" + inline-style-prefixer "^4.0.0" + rtl-css-js "^1.9.0" + sourcemap-codec "^1.4.1" + stacktrace-js "^2.0.0" + stylis "3.5.0" + nano-time@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" @@ -23530,6 +23602,21 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-use@^13.10.2: + version "13.10.2" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.10.2.tgz#4250d258ca9068662943299c01794a136408c8e9" + integrity sha512-z3VFSiPHW6arViGVnajO7YKY5OD+Z9LWcImoJdYHkau23cLSoTctxM3XENLpGxjhJlHaYiQZ6pPgq7pwGTqSZA== + dependencies: + copy-to-clipboard "^3.2.0" + nano-css "^5.2.1" + react-fast-compare "^2.0.4" + resize-observer-polyfill "^1.5.1" + screenfull "^5.0.0" + set-harmonic-interval "^1.0.1" + throttle-debounce "^2.1.0" + ts-easing "^0.2.0" + tslib "^1.10.0" + react-virtualized@^9.18.5: version "9.20.1" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.20.1.tgz#02dc08fe9070386b8c48e2ac56bce7af0208d22d" @@ -24892,6 +24979,13 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== +rtl-css-js@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.13.1.tgz#80deabf6e8f36d6767d495cd3eb60fecb20c67e1" + integrity sha512-jgkIDj6Xi25kAEm5oYM3ZMFiOQhpLEcXi2LY/6bVr91cVz73hciHKneL5AMVPxOcks/JuizSaaNsvNRkeAWe3w== + dependencies: + "@babel/runtime" "^7.1.2" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -25189,6 +25283,11 @@ scoped-regex@^1.0.0: resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg= +screenfull@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6" + integrity sha512-yShzhaIoE9OtOhWVyBBffA6V98CDCoyHTsp8228blmqYy1Z5bddzE/4FPiJKlr8DVR4VBiiUyfPzIQPIYDkeMA== + script-loader@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/script-loader/-/script-loader-0.7.2.tgz#2016db6f86f25f5cf56da38915d83378bb166ba7" @@ -25355,15 +25454,10 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -serialize-javascript@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" - integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== - -serialize-javascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.0.tgz#9310276819efd0eb128258bb341957f6eb2fc570" - integrity sha512-a/mxFfU00QT88umAJQsNWOnUKckhNCqOl028N48e7wFmo2/EHpTo9Wso+iJJCMrQnmFvcjto5RJdAHEvVhcyUQ== +serialize-javascript@^1.7.0, serialize-javascript@^2.1.0, serialize-javascript@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.1.tgz#952907a04a3e3a75af7f73d92d15e233862048b2" + integrity sha512-MPLPRpD4FNqWq9tTIjYG5LesFouDhdyH0EPY3gVK4DRD5+g4aDqdNSzLIwceulo3Yj+PL1bPh6laE5+H6LTcrQ== serve-favicon@^2.5.0: version "2.5.0" @@ -25426,6 +25520,11 @@ set-getter@^0.1.0: dependencies: to-object-path "^0.3.0" +set-harmonic-interval@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" + integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== + set-immediate-shim@^1.0.0, set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" @@ -25902,6 +26001,11 @@ source-map@0.1.32: dependencies: amdefine ">=0.0.4" +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= + "source-map@>= 0.1.2": version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" @@ -25938,6 +26042,11 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" +sourcemap-codec@^1.4.1: + version "1.4.6" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" + integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== + space-separated-tokens@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412" @@ -26139,6 +26248,13 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-generator@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.4.tgz#027513eab2b195bbb43b9c8360ba2dd0ab54de09" + integrity sha512-ha1gosTNcgxwzo9uKTQ8zZ49aUp5FIUW58YHFxCqaAHtE0XqBg0chGFYA1MfmW//x1KWq3F4G7Ug7bJh4RiRtg== + dependencies: + stackframe "^1.1.0" + stack-trace@0.0.10, stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -26149,6 +26265,11 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" integrity sha1-1PM6tU6OOHeLDKXP07OvsS22hiA= +stackframe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.0.tgz#e3fc2eb912259479c9822f7d1f1ff365bd5cbc83" + integrity sha512-Vx6W1Yvy+AM1R/ckVwcHQHV147pTPBKWCRLrXMuPrFVfvBUc3os7PR1QLIWCMhPpRg5eX9ojzbQIMLGBwyLjqg== + stackman@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/stackman/-/stackman-4.0.0.tgz#3ccdc8682fee36373ed2492dc3dad546eb44647d" @@ -26160,6 +26281,23 @@ stackman@^4.0.0: error-callsites "^2.0.2" load-source-map "^1.0.0" +stacktrace-gps@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.3.tgz#b89f84cc13bb925b96607e737b617c8715facf57" + integrity sha512-51Rr7dXkyFUKNmhY/vqZWK+EvdsfFSRiQVtgHTFlAdNIYaDD7bVh21yBHXaNWAvTD+w+QSjxHg7/v6Tz4veExA== + dependencies: + source-map "0.5.6" + stackframe "^1.1.0" + +stacktrace-js@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.1.tgz#ebdb0e9a16e6f171f96ca7878404e7f15c3d42ba" + integrity sha512-13oDNgBSeWtdGa4/2BycNyKqe+VktCoJ8VLx4pDoJkwGGJVtiHdfMOAj3aW9xTi8oR2v34z9IcvfCvT6XNdNAw== + dependencies: + error-stack-parser "^2.0.4" + stack-generator "^2.0.4" + stacktrace-gps "^3.0.3" + state-toggle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" @@ -26682,6 +26820,11 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== +stylis@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" + integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== + stylus-lookup@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-3.0.2.tgz#c9eca3ff799691020f30b382260a67355fefdddd" @@ -27525,7 +27668,7 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" -toggle-selection@^1.0.3: +toggle-selection@^1.0.3, toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= @@ -27671,6 +27814,11 @@ ts-dedent@^1.1.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-1.1.0.tgz#67983940793183dc7c7f820acb66ba02cdc33c6e" integrity sha512-CVCvDwMBWZKjDxpN3mU/Dx1v3k+sJgE8nrhXcC9vRopRfoa7vVzilNvHEAUi5jQnmFHpnxDx5jZdI1TpG8ny2g== +ts-easing@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" + integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== + ts-invariant@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.2.1.tgz#3d587f9d6e3bded97bf9ec17951dd9814d5a9d3f" @@ -27726,7 +27874,7 @@ tsd@^0.7.4: typescript "^3.0.1" update-notifier "^2.5.0" -tslib@^1: +tslib@^1, tslib@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==