diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index e1cbac0528b1f..8ad02b7162b6a 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -34,7 +34,7 @@ stage("Kibana Pipeline") { if (!IS_XPACK) { kibanaPipeline.buildOss() if (CI_GROUP == '1') { - runbld "./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh" + runbld("./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1") } } else { kibanaPipeline.buildXpack() @@ -62,18 +62,18 @@ stage("Kibana Pipeline") { def getWorkerFromParams(isXpack, job, ciGroup) { if (!isXpack) { if (job == 'firefoxSmoke') { - return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }) + return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') }) } else if(job == 'visualRegression') { - return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }) + return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }) } else { return kibanaPipeline.getOssCiGroupWorker(ciGroup) } } if (job == 'firefoxSmoke') { - return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }) + return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld('./test/scripts/jenkins_xpack_firefox_smoke.sh', 'Execute xpack-firefoxSmoke') }) } else if(job == 'visualRegression') { - return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }) + return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }) } else { return kibanaPipeline.getXpackCiGroupWorker(ciGroup) } diff --git a/.eslintrc.js b/.eslintrc.js index 16a80f01278a5..daf49d9d08281 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -214,13 +214,6 @@ module.exports = { 'jsx-a11y/click-events-have-key-events': 'off', }, }, - { - files: ['x-pack/legacy/plugins/siem/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - 'react-hooks/rules-of-hooks': 'off', - }, - }, { files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,ts,tsx}'], rules: { @@ -839,6 +832,8 @@ module.exports = { // might be introduced after the other warns are fixed // 'react/jsx-sort-props': 'error', 'react/jsx-tag-spacing': 'error', + // might be introduced after the other warns are fixed + 'react-hooks/exhaustive-deps': 'off', 'require-atomic-updates': 'error', 'rest-spread-spacing': ['error', 'never'], 'symbol-description': 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 94296d076189b..0f1136fd5334b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,11 +28,6 @@ # Canvas /x-pack/legacy/plugins/canvas/ @elastic/kibana-canvas -# Code -/x-pack/legacy/plugins/code/ @teams/code -/x-pack/test/functional/apps/code/ @teams/code -/x-pack/test/api_integration/apis/code/ @teams/code - # Logs & Metrics UI /x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui /x-pack/legacy/plugins/integrations_manager/ @elastic/epm diff --git a/.i18nrc.json b/.i18nrc.json index 51727ce014f58..d0d8beb6f5337 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -6,9 +6,9 @@ "dashboardEmbeddableContainer": "src/plugins/dashboard_embeddable_container", "data": ["src/legacy/core_plugins/data", "src/plugins/data"], "embeddableApi": "src/plugins/embeddable", + "share": "src/plugins/share", "esUi": "src/plugins/es_ui_shared", - "expressions_np": "src/plugins/expressions", - "expressions": "src/legacy/core_plugins/expressions", + "expressions": "src/plugins/expressions", "inputControl": "src/legacy/core_plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", @@ -34,7 +34,7 @@ "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": "src/legacy/core_plugins/vis_type_timeseries", "visTypeVega": "src/legacy/core_plugins/vis_type_vega", - "visualizations": "src/plugins/visualizations" + "visualizations": ["src/plugins/visualizations", "src/legacy/core_plugins/visualizations"] }, "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], "translations": [] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2903f23f55c9e..64a1dd0526d58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -325,7 +325,7 @@ Note that for VSCode, to enable "live" linting of TypeScript (and other) file ty "eslint.autoFixOnSave": true, ``` -It is **not** recommended to use `prettier` plugin on Kibana project. Because settings are in `eslintrc.js` file and it is applied to too many files that shouldn't be prettier-ized. +:warning: It is **not** recommended to use the [`Prettier` extension/IDE plugin](https://prettier.io/) while maintaining the Kibana project. Formatting and styling roles are set in the multiple `.eslintrc.js` files across the project and some of them use the [NPM version of Prettier](https://www.npmjs.com/package/prettier). Using the IDE extension might cause conflicts, applying the formatting to too many files that shouldn't be prettier-ized and/or highlighting errors that are actually OK. ### Internationalization diff --git a/Jenkinsfile b/Jenkinsfile index 8d8579736f639..c002832d4d51a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,9 +24,9 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'oss-ciGroup10': kibanaPipeline.getOssCiGroupWorker(10), 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), - 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), - 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld './test/scripts/jenkins_accessibility.sh' }), - 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), + 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') }), + 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld('./test/scripts/jenkins_accessibility.sh', 'Execute kibana-accessibility') }), + 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }), ]), 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), @@ -39,9 +39,9 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'xpack-ciGroup8': kibanaPipeline.getXpackCiGroupWorker(8), 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), - 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), - 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld './test/scripts/jenkins_xpack_accessibility.sh' }), - 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), + 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld('./test/scripts/jenkins_xpack_firefox_smoke.sh', 'Execute xpack-firefoxSmoke') }), + 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld('./test/scripts/jenkins_xpack_accessibility.sh', 'Execute xpack-accessibility') }), + 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }), ]), ]) } diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 5fd3ef5e8ff4b..461d51a3e76e3 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -120,6 +120,7 @@ You should prefer modern language features in a lot of cases, e.g.: * Prefer arrow function over storing `this` (no `const self = this;`) * Prefer template strings over string concatenation * Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()` +* Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities) ### Avoid mutability and state diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 119ede16fe1e7..412019efc7f35 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -55,7 +55,7 @@ image::images/canvas-create-URL.gif[Create POST URL] [[add-workpad-website]] === Share the workpad on a website -beta[] Download the workpad and share it on any website, then customize the workpad behavior to autoplay the pages or hide the toolbar. +beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. . If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. @@ -74,7 +74,7 @@ NOTE: Shareable workpads encode the current state of the workpad in a JSON file. [float] [[change-the-workpad-settings]] -=== Change the shareable workpad settings +=== Change the settings After you've added the workpad to your website, you can change the autoplay and toolbar settings. diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md index c6920fc30d4ee..fdf56012e4729 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md @@ -15,11 +15,6 @@ export interface ChromeNavControl | Property | Type | Description | | --- | --- | --- | +| [mount](./kibana-plugin-public.chromenavcontrol.mount.md) | MountPoint | | | [order](./kibana-plugin-public.chromenavcontrol.order.md) | number | | -## Methods - -| Method | Description | -| --- | --- | -| [mount(targetDomElement)](./kibana-plugin-public.chromenavcontrol.mount.md) | | - diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md index 6ce5d7f0d5c4d..3e1f5a1f78f89 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md @@ -2,21 +2,10 @@ [Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) > [mount](./kibana-plugin-public.chromenavcontrol.mount.md) -## ChromeNavControl.mount() method +## ChromeNavControl.mount property Signature: ```typescript -mount(targetDomElement: HTMLElement): () => void; +mount: MountPoint; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| targetDomElement | HTMLElement | | - -Returns: - -`() => void` - diff --git a/docs/development/core/public/kibana-plugin-public.mountpoint.md b/docs/development/core/public/kibana-plugin-public.mountpoint.md index 58f407904a576..928d22f00ed00 100644 --- a/docs/development/core/public/kibana-plugin-public.mountpoint.md +++ b/docs/development/core/public/kibana-plugin-public.mountpoint.md @@ -9,5 +9,5 @@ A function that should mount DOM content inside the provided container element a Signature: ```typescript -export declare type MountPoint = (element: HTMLElement) => UnmountCallback; +export declare type MountPoint = (element: T) => UnmountCallback; ``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.md b/docs/development/core/public/kibana-plugin-public.overlaystart.md index 6bcf0a581df80..8b6f11bd819f8 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.md @@ -16,6 +16,6 @@ export interface OverlayStart | Property | Type | Description | | --- | --- | --- | | [banners](./kibana-plugin-public.overlaystart.banners.md) | OverlayBannersStart | [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | -| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | (flyoutChildren: React.ReactNode, flyoutProps?: {
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | -| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | (modalChildren: React.ReactNode, modalProps?: {
className?: string;
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | +| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | OverlayFlyoutStart['open'] | | +| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | OverlayModalStart['open'] | | diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md index 6d015d6a34382..ad3351fb4d098 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md @@ -4,11 +4,9 @@ ## OverlayStart.openFlyout property + Signature: ```typescript -openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; +openFlyout: OverlayFlyoutStart['open']; ``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md index a4569e178f17d..2c983d6151f4c 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md @@ -4,12 +4,9 @@ ## OverlayStart.openModal property + Signature: ```typescript -openModal: (modalChildren: React.ReactNode, modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; +openModal: OverlayModalStart['open']; ``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md index 6431589c55bd1..71a7fd8fb6a22 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md @@ -21,7 +21,7 @@ Each route can have only one handler function, which is executed when the route ```ts const router = createRouter(); -// handler is called when '${my-plugin-id}/path' resource is requested with `GET` method +// handler is called when '/path' resource is requested with `GET` method router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); ``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index d943ad53af843..dba0ad8c8560c 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -27,7 +27,7 @@ export interface HttpServiceSetup ## Example -To handle an incoming request in your plugin you should: - Create a `Router` instance. Router is already configured to use `plugin-id` to prefix path segment for your routes. +To handle an incoming request in your plugin you should: - Create a `Router` instance. ```ts const router = httpSetup.createRouter(); @@ -61,7 +61,7 @@ const handler = async (context: RequestHandlerContext, request: KibanaRequest, r } ``` -- Register route handler for GET request to 'my-app/path/{id}' path +- Register route handler for GET request to 'path/{id}' path ```ts import { schema, TypeOf } from '@kbn/config-schema'; diff --git a/docs/images/lens_data_info.gif b/docs/images/lens_data_info.gif new file mode 100644 index 0000000000000..e2c565de9f6a7 Binary files /dev/null and b/docs/images/lens_data_info.gif differ diff --git a/docs/images/lens_drag_drop.gif b/docs/images/lens_drag_drop.gif new file mode 100644 index 0000000000000..39cde64fb97eb Binary files /dev/null and b/docs/images/lens_drag_drop.gif differ diff --git a/docs/images/lens_remove_layer.png b/docs/images/lens_remove_layer.png new file mode 100644 index 0000000000000..4184e5b846870 Binary files /dev/null and b/docs/images/lens_remove_layer.png differ diff --git a/docs/images/lens_suggestions.gif b/docs/images/lens_suggestions.gif new file mode 100644 index 0000000000000..0452207b86456 Binary files /dev/null and b/docs/images/lens_suggestions.gif differ diff --git a/docs/images/lens_tutorial_1.png b/docs/images/lens_tutorial_1.png new file mode 100644 index 0000000000000..7992276c833e7 Binary files /dev/null and b/docs/images/lens_tutorial_1.png differ diff --git a/docs/images/lens_tutorial_2.png b/docs/images/lens_tutorial_2.png new file mode 100644 index 0000000000000..b47e7feff3b9f Binary files /dev/null and b/docs/images/lens_tutorial_2.png differ diff --git a/docs/images/lens_tutorial_3.png b/docs/images/lens_tutorial_3.png new file mode 100644 index 0000000000000..ea40b458202b7 Binary files /dev/null and b/docs/images/lens_tutorial_3.png differ diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index caff7f5b1fdc6..38fceeb47d6fd 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -9,11 +9,12 @@ for displayed decimal values. . Scroll or search for the setting you want to modify. . Enter a new value for the setting. + [float] [[settings-read-only-access]] === [xpack]#Read only access# -When you have insufficient privileges to edit advanced settings, the following -indicator in Kibana will be displayed. The buttons to edit settings won't be visible. +When you have insufficient privileges to edit advanced settings, the following +indicator in Kibana will be displayed. The buttons to edit settings won't be visible. For more information on granting access to Kibana see <>. [role="screenshot"] @@ -25,9 +26,9 @@ image::images/settings-read-only-badge.png[Example of Advanced Settings Manageme WARNING: Modifying a setting can affect {kib} performance and cause problems that are -difficult to diagnose. Setting a property value to a blank field reverts +difficult to diagnose. Setting a property value to a blank field reverts to the default behavior, which might not be -compatible with other configuration settings. Deleting a custom setting +compatible with other configuration settings. Deleting a custom setting removes it from {kib} permanently. @@ -44,7 +45,7 @@ removes it from {kib} permanently. adapt to the interval between measurements. Keys are http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. `dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser. `dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type]. -`defaultIndex`:: The index to access if no index is set. The default is `null`. +`defaultIndex`:: The index to access if no index is set. The default is `null`. `fields:popularLimit`:: The top N most popular fields to show. `filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. `filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. @@ -59,46 +60,46 @@ mentioned use "\_default_". `histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values when necessary. `history:limit`:: In fields that have history, such as query inputs, show this many recent values. -`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names, +`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names, look for this many recent matching patterns from which to query the field mapping. `indexPattern:placeholder`:: The default placeholder value to use in Management > Index Patterns > Create Index Pattern. -`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields +`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields into the document when displaying it. `metrics:max_buckets`:: The maximum numbers of buckets that a single -data source can return. This might arise when the user selects a +data source can return. This might arise when the user selects a short interval (for example, 1s) for a long time period (1 year). -`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character -in a query clause. Only applies when experimental query features are -enabled in the query bar. To disallow leading wildcards in Lucene queries, +`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character +in a query clause. Only applies when experimental query features are +enabled in the query bar. To disallow leading wildcards in Lucene queries, use `query:queryString:options`. `query:queryString:options`:: Options for the Lucene query string parser. Only used when "Query language" is set to Lucene. -`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. +`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. The default value is 1000. Do not set above 10000. -`savedObjects:perPage`:: The number of objects to show on each page of the +`savedObjects:perPage`:: The number of objects to show on each page of the list of saved objects. The default is 5. `search:queryLanguage`:: The query language to use in the query bar. -Choices are <>, a language built specifically for {kib}, and the <>, a language built specifically for {kib}, and the <>. -`shortDots:enable`:: Set this property to `true` to shorten long +`shortDots:enable`:: Set this property to `true` to shorten long field names in visualizations. For example, show `f.b.baz` instead of `foo.bar.baz`. `sort:options`:: Options for the Elasticsearch {ref}/search-request-body.html#request-body-search-sort[sort] parameter. -`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the -URL, which can lead to problems when there is a lot of state information, -and the URL gets very long. -Enabling this setting stores part of the URL in your browser session to keep the +`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the +URL, which can lead to problems when there is a lot of state information, +and the URL gets very long. +Enabling this setting stores part of the URL in your browser session to keep the URL short. `theme:darkMode`:: Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page to apply the setting. -`timepicker:quickRanges`:: The list of ranges to show in the Quick section of -the time filter. This should be an array of objects, with each object containing -`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), +`timepicker:quickRanges`:: The list of ranges to show in the Quick section of +the time filter. This should be an array of objects, with each object containing +`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), and `display` (the title to be displayed). `timepicker:refreshIntervalDefaults`:: The default refresh interval for the time filter. Example: `{ "display": "15 seconds", "pause": true, "value": 15000 }`. `timepicker:timeDefaults`:: The default selection in the time filter. `truncate:maxHeight`:: The maximum height that a cell occupies in a table. Set to 0 to disable truncation. -`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as +`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as cluster alert notifications from Monitoring. @@ -107,7 +108,7 @@ cluster alert notifications from Monitoring. === Accessibility settings [horizontal] -`accessibility:disableAnimations`:: Turns off all unnecessary animations in the +`accessibility:disableAnimations`:: Turns off all unnecessary animations in the {kib} UI. Refresh the page to apply the changes. [float] @@ -124,21 +125,21 @@ cluster alert notifications from Monitoring. [horizontal] `context:defaultSize`:: The number of surrounding entries to display in the context view. The default value is 5. `context:step`:: The number by which to increment or decrement the context size. The default value is 5. -`context:tieBreakerFields`:: A comma-separated list of fields to use -for breaking a tie between documents that have the same timestamp value. The first +`context:tieBreakerFields`:: A comma-separated list of fields to use +for breaking a tie between documents that have the same timestamp value. The first field that is present and sortable in the current index pattern is used. `defaultColumns`:: The columns that appear by default on the Discover page. -The default is `_source`. -`discover:aggs:terms:size`:: The number terms that are visualized when clicking +The default is `_source`. +`discover:aggs:terms:size`:: The number terms that are visualized when clicking the Visualize button in the field drop down. The default is `20`. `discover:sampleSize`:: The number of rows to show in the Discover table. `discover:sort:defaultOrder`:: The default sort direction for time-based index patterns. -`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. +`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. This setting does not have an effect when loading a saved search. `doc_table:hideTimeColumn`:: Hides the "Time" column in Discover and in all saved searches on dashboards. -`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. +`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. Highlighting slows requests when -working on big documents. +working on big documents. @@ -150,14 +151,14 @@ working on big documents. [horizontal] `notifications:banner`:: A custom banner intended for temporary notices to all users. Supports https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. -`notifications:lifetime:banner`:: The duration, in milliseconds, for banner -notification displays. The default value is 3000000. Set this field to `Infinity` +`notifications:lifetime:banner`:: The duration, in milliseconds, for banner +notification displays. The default value is 3000000. Set this field to `Infinity` to disable banner notifications. -`notifications:lifetime:error`:: The duration, in milliseconds, for error +`notifications:lifetime:error`:: The duration, in milliseconds, for error notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications. -`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. +`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. The default value is 5000. Set this field to `Infinity` to disable information notifications. -`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification +`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications. @@ -175,8 +176,8 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa === Rollup settings [horizontal] -`rollups:enableIndexPatterns`:: Enables the creation of index patterns that -capture rollup indices, which in turn enables visualizations based on rollup data. +`rollups:enableIndexPatterns`:: Enables the creation of index patterns that +capture rollup indices, which in turn enables visualizations based on rollup data. Refresh the page to apply the changes. @@ -188,22 +189,22 @@ Refresh the page to apply the changes. `courier:batchSearches`:: When disabled, dashboard panels will load individually, and search requests will terminate when users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and searches will not terminate. -`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] +`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] to use when `courier:setRequestPreference` is set to "custom". -`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. +`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. Useful when dashboards consist of visualizations from multiple index patterns. -`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] -setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this +`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] +setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this config and use the {es} default. `courier:setRequestPreference`:: Enables you to set which shards handle your search requests. -* *Session ID:* Restricts operations to execute all search requests on the same shards. +* *Session ID:* Restricts operations to execute all search requests on the same shards. This has the benefit of reusing shard caches across requests. -* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` +* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` to customize your preference value. -* *None:* Do not set a preference. This might provide better performance -because requests can be spread across all shard copies. However, results might +* *None:* Do not set a preference. This might provide better performance +because requests can be spread across all shard copies. However, results might be inconsistent because different shards might be in different refresh states. -`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. +`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. Searching through frozen indices might increase the search time. This setting is off by default. Users must opt-in to include frozen indices. @@ -212,8 +213,8 @@ might increase the search time. This setting is off by default. Users must opt-i === SIEM settings [horizontal] -`siem:defaultAnomalyScore`:: The threshold above which anomalies are displayed in the SIEM app. -`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. +`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app. +`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. `siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds. `siem:timeDefaults`:: The default period of time in the SIEM time filter. @@ -226,16 +227,16 @@ might increase the search time. This setting is off by default. Users must opt-i `timelion:default_rows`:: The default number of rows to use on a Timelion sheet. `timelion:es.default_index`:: The default index when using the `.es()` query. `timelion:es.timefield`:: The default field containing a timestamp when using the `.es()` query. -`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host -in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be +`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host +in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be selected from a whitelist configured in the `kibana.yml` under `timelion.graphiteUrls`. `timelion:max_buckets`:: The maximum number of buckets a single data source can return. This value is used for calculating automatic intervals in visualizations. `timelion:min_interval`:: The smallest interval to calculate when using "auto". `timelion:quandl.key`:: [experimental] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com]. -`timelion:showTutorial`:: Shows the Timelion tutorial +`timelion:showTutorial`:: Shows the Timelion tutorial to users when they first open the Timelion app. -`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, +`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, this is the number of buckets to try to represent. @@ -246,18 +247,18 @@ this is the number of buckets to try to represent. [horizontal] `visualization:colorMapping`:: Maps values to specified colors in visualizations. -`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed -when highlighting another element of the chart. The lower this number, the more +`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed +when highlighting another element of the chart. The lower this number, the more the highlighted element stands out. This must be a number between 0 and 1. -`visualization:loadingDelay`:: The time to wait before dimming visualizations +`visualization:loadingDelay`:: The time to wait before dimming visualizations during a query. -`visualization:regionmap:showWarnings`:: Shows +`visualization:regionmap:showWarnings`:: Shows a warning in a region map when terms cannot be joined to a shape. `visualization:tileMap:WMSdefaults`:: The default properties for the WMS map server support in the coordinate map. `visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, -and 12 is the maximum. See this +and 12 is the maximum. See this {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions]. -`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. +`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. If disabled, only visualizations that are considered production-ready are available to the user. @@ -265,6 +266,5 @@ If disabled, only visualizations that are considered production-ready are availa [[kibana-telemetry-settings]] === Usage data settings -Helps improve the Elastic Stack by providing usage statistics for +Helps improve the Elastic Stack by providing usage statistics for basic features. This data will not be shared outside of Elastic. - diff --git a/docs/management/managing-beats.asciidoc b/docs/management/managing-beats.asciidoc index fb46d66611dfa..13e8f52f29b87 100644 --- a/docs/management/managing-beats.asciidoc +++ b/docs/management/managing-beats.asciidoc @@ -2,7 +2,7 @@ [role="xpack"] == Managing {beats} -beta[] +include::{asciidoc-dir}/../../shared/discontinued.asciidoc[tag=cm-discontinued] Use the Central Management UI under *Management > {beats}* to define and manage configurations in a central location in {kib} and quickly deploy diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index de72c32f153d1..97b10e389963e 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -14,7 +14,7 @@ You can create a layer that requests data from {es} from the following: ** Grid aggregation source -** <>. The search context is applied to both the terms join and the vector source when the vector source is provided by Elasticsearch documents. +** <> * <> with Grid aggregation source @@ -87,8 +87,11 @@ The most common cause for empty layers are searches for a field that exists in o [[maps-disable-search-for-layer]] ==== Disable search for layer -To prevent the global search bar from applying search context to a layer, clear the *Apply global filter to layer* checkbox in Layer settings. -Disabling the search context applies to the layer source and all <> configured for the layer. +You can prevent the search bar from applying search context to a layer by configuring the following: + +* In *Source settings*, clear the *Apply global filter to source* checkbox to turn off the global search context for the layer source. + +* In *Term joins*, clear the *Apply global filter to join* checkbox to turn off the global search context for the <>. [float] [[maps-add-index-search]] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 11a50fea92f05..f4434ea7a09f4 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -320,6 +320,18 @@ supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2 setting this to `true` enables unauthenticated users to access the Kibana server status API and status page. +`telemetry.allowChangingOptInStatus`:: *Default: true*. If `true`, +users are able to change the telemetry setting at a later time in +<>. If `false`, +{kib} looks at the value of `telemetry.optIn` to determine whether to send +telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn` +cannot be `false` at the same time. + +`telemetry.optIn`:: *Default: true* If `true`, telemetry data is sent to Elastic. + If `false`, collection of telemetry data is disabled. + To enable telemetry and prevent users from disabling it, + set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. + `vega.enableExternalUrls:`:: *Default: false* Set this value to true to allow Vega to use any URL to access external data sources and images. If false, Vega can only get data from Elasticsearch. `xpack.license_management.enabled`:: *Default: true* Set this value to false to diff --git a/docs/user/reporting/development/index.asciidoc b/docs/user/reporting/development/index.asciidoc index 2a9abae34f042..a64e540da0c70 100644 --- a/docs/user/reporting/development/index.asciidoc +++ b/docs/user/reporting/development/index.asciidoc @@ -14,9 +14,7 @@ However, these docs will be kept up-to-date to reflect the current implementatio [float] [[reporting-nav-bar-extensions]] === Share menu extensions -X-Pack uses the `ShareContextMenuExtensionsRegistryProvider` to register actions in the share menu. - -This integration will likely be changing in the near future as we move towards a unified actions abstraction across {kib}. +X-Pack uses the `share` plugin of the Kibana platform to register actions in the share menu. [float] === Generate job URL diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index eec9ef65cba90..ed74525d22e7c 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -24,9 +24,10 @@ To create a visualization: . Click on *Visualize* in the side navigation. . Click the *Create new visualization* button or the **+** button. . Choose the visualization type: -+ + * *Basic charts* [horizontal] +<>:: Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. <>:: Compare different series in X/Y charts. <>:: Shade cells within a matrix. <>:: Display each source's contribution to a total. @@ -142,6 +143,8 @@ include::{kib-repo-dir}/visualize/saving.asciidoc[] include::{kib-repo-dir}/visualize/visualize_rollup_data.asciidoc[] +include::{kib-repo-dir}/visualize/lens.asciidoc[] + include::{kib-repo-dir}/visualize/xychart.asciidoc[] include::{kib-repo-dir}/visualize/controls.asciidoc[] diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc new file mode 100644 index 0000000000000..086f88c636c81 --- /dev/null +++ b/docs/visualize/lens.asciidoc @@ -0,0 +1,186 @@ +[role="xpack"] +[[lens]] +== Lens + +beta[] + +*Lens* provides you with a simple and fast way to create visualizations from your Elasticsearch data. With Lens, you can: + +* Quickly build visualizations by dragging and dropping data fields. + +* Understand your data with a summary view on each field. + +* Easily change the visualization type by selecting the automatically generated visualization suggestions. + +* Save your visualization for use in a dashboard. + +[float] +[[drag-drop]] +=== Drag and drop + +The data panel in the left column shows the data fields for the selected time period. When +you drag a field from the data panel, Lens highlights where you can drop that field. The first time you drag a data field, +you'll see two places highlighted in green: + +* The visualization builder pane + +* The *X-axis* or *Y-axis* fields in the right column + +You can incorporate many fields into your visualization, and Lens uses heuristics to decide how +to apply each one to the visualization. + +[role="screenshot"] +image::images/lens_drag_drop.gif[] + +TIP: Drag-and-drop capabilities are available only when Lens knows how to use the data. You can still customize +your visualization if Lens is unable to make a suggestion. + +[float] +[[apply-lens-filters]] +==== Find the right data + +Lens shows you fields based on the <> you have defined in +{kib}, and the current time range. When you change the index pattern or time filter, +the list of fields are updated. + +To narrow the list of fields you see in the left panel, you can: + +* Enter the field name in *Search field names*. + +* Click *Filter by type*, then select the filter. You can also select *Only show fields with data* +to show the full list of fields from the index pattern. + +[float] +[[view-data-summaries]] +==== Data summaries + +To help you decide exactly the data you want to display, get a quick summary of each data field. +The summary shows the distribution of values in the time range. + +To view the data information, navigate to a data field, then click *i*. + +[role="screenshot"] +image::images/lens_data_info.gif[] + +[float] +[[change-the-visualization-type]] +==== Change the visualization type + +With Lens, you are no longer required to build each visualization from scratch. Lens allows +you to switch between any supported chart type at any time. Lens also provides +suggestions, which are shortcuts to alternate visualizations based on the data you have. + +You can switch between suggestions without losing your previous state: + +[role="screenshot"] +image::images/lens_suggestions.gif[] + +If you want to switch to a chart type that is not suggested, click the chart type in the +top right, then select a chart type. When there is an exclamation point (!) +next to a chart type, Lens is unable to transfer your current data, but +still allows you to make the change. + +[float] +[[customize-operation]] +==== Customize the data for your visualization + +Lens allows some customizations of the data for each visualization. + +. Change the index pattern. + +.. In the left column, click the index pattern name. + +.. Select the new index pattern. ++ +If there is a match, Lens displays the new data. All fields that do not match the index pattern are removed. + +. Change the data field options, such as the aggregation or label. + +.. Click *Drop a field here* or the field name in the right column. + +.. Change the options that appear depending on the type of field. + +[float] +[[layers]] +==== Layers in bar, line, and area charts + +The bar, line, and area charts allow you to layer two different series. To add a layer, click *+*. + +To remove a layer, click the chart icon next to the index name: + +[role="screenshot"] +image::images/lens_remove_layer.png[] + +[float] +[[lens-tutorial]] +=== Lens tutorial + +Ready to create your own visualization with Lens? Use the following tutorial to create a visualization that +lets you compare sales over time. + +[float] +[[lens-before-begin]] +==== Before you begin + +To start, you'll need to add the <>. + +[float] +==== Build the visualization + +Drag and drop your data onto the visualization builder pane. + +. Open *Visualize*, then click *Create visualization*. + +. On the *New Visualization* window, click *Lens*. + +. In the left column, select the *kibana_sample_data_ecommerce* index. + +. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. The list of data fields are updated. + +. Drag and drop the *taxful_total_price* data field to the visualization builder pane. ++ +[role="screenshot"] +image::images/lens_tutorial_1.png[Lens tutorial] + +Lens has taken your intent to see *taxful_total_price* and added in the *order_date* field to show +average order prices over time. + +To break down your data, drag the *category.keyword* field to the visualization builder pane. Lens +understands that you want to show the top categories and compare them across the dates, +and creates a chart that compares the sales for each of the top 3 categories: + +[role="screenshot"] +image::images/lens_tutorial_2.png[Lens tutorial] + +[float] +[[customize-lens-visualization]] +==== Further customization + +Customize your visualization to look exactly how you want. + +. In the right column, click *Average of taxful_total_price*. + +.. Change the *Label* to `Sales`, or a name that you prefer for the data. + +. Click *Top values of category.keyword*. + +.. Increase *Number of values* to `10`. The visualization updates in the background to show there are only +six available categories. + +. Look at the suggestions. None of them show an area chart, but for sales data, a stacked area chart +might make sense. To switch the chart type: + +.. Click *Stacked bar chart* in the right column. + +.. Click *Stacked area*. ++ +[role="screenshot"] +image::images/lens_tutorial_3.png[Lens tutorial] + +[float] +[[lens-tutorial-next-steps]] +==== Next steps + +Now that you've created your visualization in Lens, you can add it to a Dashboard. + +For more information, see <>. diff --git a/package.json b/package.json index 8fa9bf1847eb8..ac06753d7d046 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "**/@types/react": "16.8.3", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", - "**/typescript": "3.5.3", + "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9" @@ -327,6 +327,7 @@ "@types/pngjs": "^3.3.2", "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", + "@types/reach__router": "^1.2.6", "@types/react": "^16.8.0", "@types/react-dom": "^16.8.0", "@types/react-redux": "^6.0.6", @@ -346,8 +347,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.5.0", - "@typescript-eslint/parser": "^2.5.0", + "@typescript-eslint/eslint-plugin": "^2.8.0", + "@typescript-eslint/parser": "^2.8.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -360,7 +361,7 @@ "chance": "1.0.18", "cheerio": "0.22.0", "chokidar": "3.2.1", - "chromedriver": "^77.0.0", + "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", @@ -444,7 +445,7 @@ "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", "tree-kill": "^1.2.1", - "typescript": "3.5.3", + "typescript": "3.7.2", "typings-tester": "^0.3.2", "vinyl-fs": "^3.0.3", "xml2js": "^0.4.22", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index b5079a49c8385..71517bc10404d 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.5.0", - "@typescript-eslint/parser": "^2.5.0", + "@typescript-eslint/eslint-plugin": "^2.8.0", + "@typescript-eslint/parser": "^2.8.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index b0ac86b465a62..f59fbf4720835 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -17,6 +17,6 @@ "@babel/cli": "7.5.5", "@kbn/dev-utils": "1.0.0", "@kbn/babel-preset": "1.0.0", - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d2bad41e8de86..d1b7bc20dd9f9 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -27,6 +27,13 @@ const plugins = [ // // See https://github.com/babel/proposals/issues/12 for progress require.resolve('@babel/plugin-proposal-class-properties'), + + // Optional Chaining proposal is stage 3 (https://github.com/tc39/proposal-optional-chaining) + // Need this since we are using TypeScript 3.7+ + require.resolve('@babel/plugin-proposal-optional-chaining'), + // Nullish coalescing proposal is stage 3 (https://github.com/tc39/proposal-nullish-coalescing) + // 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'; diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index c22cf175b29e5..1913301e21a76 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -5,6 +5,8 @@ "license": "Apache-2.0", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.5.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", + "@babel/plugin-proposal-optional-chaining": "^7.6.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.5.0", "@babel/preset-env": "^7.5.5", diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index 4880fb4ebfdee..71c0ae4bff1f9 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -10,7 +10,7 @@ "kbn:bootstrap": "yarn build" }, "devDependencies": { - "typescript": "3.5.3" + "typescript": "3.7.2" }, "peerDependencies": { "joi": "^13.5.2", diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index e8781f6d901d9..09753afeb120f 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -21,7 +21,7 @@ "tslib": "^1.9.3" }, "devDependencies": { - "typescript": "3.5.3", + "typescript": "3.7.2", "@kbn/expect": "1.0.0", "chance": "1.0.18" } diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json index abfaea75357dd..9532983942d6b 100644 --- a/packages/kbn-elastic-idx/package.json +++ b/packages/kbn-elastic-idx/package.json @@ -20,7 +20,7 @@ "@babel/core": "^7.5.5", "@babel/plugin-transform-async-to-generator": "^7.5.0", "jest": "^24.9.0", - "typescript": "3.5.3" + "typescript": "3.7.2" }, "jest": { "testEnvironment": "node" diff --git a/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js b/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js deleted file mode 100644 index d9f559987f58b..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import _ from 'lodash'; -import { migrateFilter } from '../migrate_filter'; - -describe('migrateFilter', function () { - - const oldMatchPhraseFilter = { - match: { - fieldFoo: { - query: 'foobar', - type: 'phrase' - } - } - }; - - const newMatchPhraseFilter = { - match_phrase: { - fieldFoo: { - query: 'foobar' - } - } - }; - - // https://github.com/elastic/elasticsearch/pull/17508 - it('should migrate match filters of type phrase', function () { - const migratedFilter = migrateFilter(oldMatchPhraseFilter); - expect(_.isEqual(migratedFilter, newMatchPhraseFilter)).to.be(true); - }); - - it('should not modify the original filter', function () { - const oldMatchPhraseFilterCopy = _.clone(oldMatchPhraseFilter, true); - migrateFilter(oldMatchPhraseFilter); - expect(_.isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).to.be(true); - }); - - it('should return the original filter if no migration is necessary', function () { - const originalFilter = { - match_all: {} - }; - const migratedFilter = migrateFilter(originalFilter); - expect(migratedFilter).to.be(originalFilter); - expect(_.isEqual(migratedFilter, originalFilter)).to.be(true); - }); - -}); diff --git a/packages/kbn-es-query/src/es_query/__tests__/from_filters.js b/packages/kbn-es-query/src/es_query/__tests__/from_filters.js deleted file mode 100644 index 676992e4dddc8..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/from_filters.js +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { buildQueryFromFilters } from '../from_filters'; - -describe('build query', function () { - describe('buildQueryFromFilters', function () { - it('should return the parameters of an Elasticsearch bool query', function () { - const result = buildQueryFromFilters([]); - const expected = { - must: [], - filter: [], - should: [], - must_not: [], - }; - expect(result).to.eql(expected); - }); - - it('should transform an array of kibana filters into ES queries combined in the bool clauses', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all' }, - }, - { - exists: { field: 'foo' }, - meta: { type: 'exists' }, - }, - ]; - - const expectedESQueries = [ - { match_all: {} }, - { exists: { field: 'foo' } }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should remove disabled filters', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all', negate: true, disabled: true }, - }, - ]; - - const expectedESQueries = []; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - }); - - it('should remove falsy filters', function () { - const filters = [null, undefined]; - - const expectedESQueries = []; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - expect(result.must).to.eql(expectedESQueries); - }); - - it('should place negated filters in the must_not clause', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all', negate: true }, - }, - ]; - - const expectedESQueries = [{ match_all: {} }]; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - }); - - it('should translate old ES filter syntax into ES 5+ query objects', function () { - const filters = [ - { - query: { exists: { field: 'foo' } }, - meta: { type: 'exists' }, - }, - ]; - - const expectedESQueries = [ - { - exists: { field: 'foo' }, - }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should migrate deprecated match syntax', function () { - const filters = [ - { - query: { match: { extension: { query: 'foo', type: 'phrase' } } }, - meta: { type: 'phrase' }, - }, - ]; - - const expectedESQueries = [ - { - match_phrase: { extension: { query: 'foo' } }, - }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should not add query:queryString:options to query_string filters', function () { - const filters = [ - { - query: { query_string: { query: 'foo' } }, - meta: { type: 'query_string' }, - }, - ]; - const expectedESQueries = [{ query_string: { query: 'foo' } }]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - }); -}); diff --git a/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js b/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js deleted file mode 100644 index 4361659021bd5..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { buildQueryFromLucene } from '../from_lucene'; -import { decorateQuery } from '../decorate_query'; -import { luceneStringToDsl } from '../lucene_string_to_dsl'; - -describe('build query', function () { - - describe('buildQueryFromLucene', function () { - - it('should return the parameters of an Elasticsearch bool query', function () { - const result = buildQueryFromLucene(); - const expected = { - must: [], - filter: [], - should: [], - must_not: [], - }; - expect(result).to.eql(expected); - }); - - it('should transform an array of lucene queries into ES queries combined in the bool\'s must clause', function () { - const queries = [ - { query: 'foo:bar', language: 'lucene' }, - { query: 'bar:baz', language: 'lucene' }, - ]; - - const expectedESQueries = queries.map( - (query) => { - return decorateQuery(luceneStringToDsl(query.query), {}); - } - ); - - const result = buildQueryFromLucene(queries, {}); - - expect(result.must).to.eql(expectedESQueries); - }); - - it('should also accept queries in ES query DSL format, simply passing them through', function () { - const queries = [ - { query: { match_all: {} }, language: 'lucene' }, - ]; - - const result = buildQueryFromLucene(queries, {}); - - expect(result.must).to.eql([queries[0].query]); - }); - - }); - - it('should accept a date format in the decorated queries and combine that into the bool\'s must clause', function () { - const queries = [ - { query: 'foo:bar', language: 'lucene' }, - { query: 'bar:baz', language: 'lucene' }, - ]; - const dateFormatTZ = 'America/Phoenix'; - - const expectedESQueries = queries.map( - (query) => { - return decorateQuery(luceneStringToDsl(query.query), {}, dateFormatTZ); - } - ); - - const result = buildQueryFromLucene(queries, {}, dateFormatTZ); - - expect(result.must).to.eql(expectedESQueries); - }); - -}); diff --git a/packages/kbn-es-query/src/index.d.ts b/packages/kbn-es-query/src/index.d.ts index c06cef6367fe7..79e6903b18644 100644 --- a/packages/kbn-es-query/src/index.d.ts +++ b/packages/kbn-es-query/src/index.d.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './es_query'; export * from './kuery'; diff --git a/packages/kbn-es-query/src/index.js b/packages/kbn-es-query/src/index.js index 963999bd0999b..79e6903b18644 100644 --- a/packages/kbn-es-query/src/index.js +++ b/packages/kbn-es-query/src/index.js @@ -18,4 +18,3 @@ */ export * from './kuery'; -export * from './es_query'; diff --git a/packages/kbn-es-query/src/kuery/ast/ast.d.ts b/packages/kbn-es-query/src/kuery/ast/ast.d.ts index 06f4940e8ed3b..ef3d0ee828874 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.d.ts +++ b/packages/kbn-es-query/src/kuery/ast/ast.d.ts @@ -25,18 +25,26 @@ import { JsonObject } from '..'; export type KueryNode = any; +export type DslQuery = any; + export interface KueryParseOptions { helpers: { [key: string]: any; }; startRule: string; + allowLeadingWildcards: boolean; } export function fromKueryExpression( - expression: string, - parseOptions?: KueryParseOptions + expression: string | DslQuery, + parseOptions?: Partial ): KueryNode; -export function toElasticsearchQuery(node: KueryNode, indexPattern?: any): JsonObject; +export function toElasticsearchQuery( + node: KueryNode, + indexPattern?: any, + config?: Record, + context?: Record +): JsonObject; export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean; diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js deleted file mode 100644 index 6c149b6b8a98d..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { convertExistsFilter } from '../exists'; - -describe('filter to kuery migration', function () { - - describe('exists filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const result = convertExistsFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'exists'); - expect(result.arguments[0].value).to.be('foo'); - }); - - it('should throw an exception if the given filter is not of type "exists"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertExistsFilter).withArgs(filter).to.throwException( - /Expected filter of type "exists", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js deleted file mode 100644 index 1e5656f85eb89..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { filterToKueryAST } from '../filter_to_kuery'; - -describe('filter to kuery migration', function () { - - describe('filterToKueryAST', function () { - - it('should hand off conversion of known filter types to the appropriate converter', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const result = filterToKueryAST(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'exists'); - }); - - it('should thrown an error when an unknown filter type is encountered', function () { - const filter = { - meta: { - type: 'foo', - } - }; - - expect(filterToKueryAST).withArgs(filter).to.throwException(/Couldn't convert that filter to a kuery/); - }); - - it('should wrap the AST node of negated filters in a "not" function', function () { - const filter = { - meta: { - type: 'exists', - key: 'foo', - } - }; - const negatedFilter = _.set(_.cloneDeep(filter), 'meta.negate', true); - - const result = filterToKueryAST(filter); - const negatedResult = filterToKueryAST(negatedFilter); - - expect(negatedResult).to.have.property('type', 'function'); - expect(negatedResult).to.have.property('function', 'not'); - expect(negatedResult.arguments[0]).to.eql(result); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js deleted file mode 100644 index e4cb6d30bbf48..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { convertGeoBoundingBox } from '../geo_bounding_box'; - -describe('filter to kuery migration', function () { - - describe('geo_bounding_box filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'geo_bounding_box', - key: 'foo', - params: { - topLeft: { - lat: 10, - lon: 20, - }, - bottomRight: { - lat: 30, - lon: 40, - }, - }, - } - }; - const result = convertGeoBoundingBox(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'geoBoundingBox'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - const argByName = _.mapKeys(args, 'name'); - expect(argByName.topLeft.value.value).to.be('10, 20'); - expect(argByName.bottomRight.value.value).to.be('30, 40'); - }); - - it('should throw an exception if the given filter is not of type "geo_bounding_box"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertGeoBoundingBox).withArgs(filter).to.throwException( - /Expected filter of type "geo_bounding_box", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js deleted file mode 100644 index e1b2a09edaba3..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { convertGeoPolygon } from '../geo_polygon'; - -describe('filter to kuery migration', function () { - - describe('geo_polygon filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'geo_polygon', - key: 'foo', - params: { - points: [ - { - lat: 10, - lon: 20, - }, - { - lat: 30, - lon: 40, - }, - ] - } - } - }; - const result = convertGeoPolygon(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'geoPolygon'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - expect(args[0].value).to.be('10, 20'); - expect(args[1].value).to.be('30, 40'); - }); - - it('should throw an exception if the given filter is not of type "geo_polygon"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertGeoPolygon).withArgs(filter).to.throwException( - /Expected filter of type "geo_polygon", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js deleted file mode 100644 index b2a7c097a3f95..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { convertPhraseFilter } from '../phrase'; - -describe('filter to kuery migration', function () { - - describe('phrase filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'phrase', - key: 'foo', - params: { - query: 'bar' - }, - } - }; - const result = convertPhraseFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'is'); - - const { arguments: [ { value: fieldName }, { value: value } ] } = result; - expect(fieldName).to.be('foo'); - expect(value).to.be('bar'); - }); - - it('should throw an exception if the given filter is not of type "phrase"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertPhraseFilter).withArgs(filter).to.throwException( - /Expected filter of type "phrase", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js deleted file mode 100644 index 2cad37cc0ad3a..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { convertRangeFilter } from '../range'; - -describe('filter to kuery migration', function () { - - describe('range filter', function () { - - it('should return a kuery node equivalent to the given filter', function () { - const filter = { - meta: { - type: 'range', - key: 'foo', - params: { - gt: 1000, - lt: 8000, - }, - } - }; - const result = convertRangeFilter(filter); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'range'); - - const { arguments: [ { value: fieldName }, ...args ] } = result; - expect(fieldName).to.be('foo'); - - const argByName = _.mapKeys(args, 'name'); - expect(argByName.gt.value.value).to.be(1000); - expect(argByName.lt.value.value).to.be(8000); - }); - - it('should throw an exception if the given filter is not of type "range"', function () { - const filter = { - meta: { - type: 'foo' - } - }; - - expect(convertRangeFilter).withArgs(filter).to.throwException( - /Expected filter of type "range", got "foo"/ - ); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js deleted file mode 100644 index 18c53938d6c36..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js +++ /dev/null @@ -1,54 +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 { nodeTypes } from '../node_types'; -import { convertPhraseFilter } from './phrase'; -import { convertRangeFilter } from './range'; -import { convertExistsFilter } from './exists'; -import { convertGeoBoundingBox } from './geo_bounding_box'; -import { convertGeoPolygon } from './geo_polygon'; - -const conversionChain = [ - convertPhraseFilter, - convertRangeFilter, - convertExistsFilter, - convertGeoBoundingBox, - convertGeoPolygon, -]; - -export function filterToKueryAST(filter) { - const { negate } = filter.meta; - - const node = conversionChain.reduce((acc, converter) => { - if (acc !== null) return acc; - - try { - return converter(filter); - } - catch (ex) { - return null; - } - }, null); - - if (!node) { - throw new Error(`Couldn't convert that filter to a kuery`); - } - - return negate ? nodeTypes.function.buildNode('not', node) : node; -} diff --git a/packages/kbn-es-query/src/kuery/filter_migration/range.js b/packages/kbn-es-query/src/kuery/filter_migration/range.js deleted file mode 100644 index 438ab979e1395..0000000000000 --- a/packages/kbn-es-query/src/kuery/filter_migration/range.js +++ /dev/null @@ -1,29 +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 { nodeTypes } from '../node_types'; - -export function convertRangeFilter(filter) { - if (filter.meta.type !== 'range') { - throw new Error(`Expected filter of type "range", got "${filter.meta.type}"`); - } - - const { key, params } = filter.meta; - return nodeTypes.function.buildNode('range', key, params); -} diff --git a/packages/kbn-es-query/src/kuery/index.js b/packages/kbn-es-query/src/kuery/index.js index 08fa9829d4a56..e0cacada7f274 100644 --- a/packages/kbn-es-query/src/kuery/index.js +++ b/packages/kbn-es-query/src/kuery/index.js @@ -18,6 +18,5 @@ */ export * from './ast'; -export * from './filter_migration'; export { nodeTypes } from './node_types'; export * from './errors'; diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 8a88626bffbe8..3e25ceb8714df 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -21,7 +21,7 @@ "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0", - "typescript": "3.5.3" + "typescript": "3.7.2" }, "dependencies": { "intl-format-cache": "^2.1.0", diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index ac46dd02757cf..2f9b177be6532 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -58,7 +58,7 @@ "strip-ansi": "^4.0.0", "strong-log-transformer": "^2.1.0", "tempy": "^0.3.0", - "typescript": "3.5.3", + "typescript": "3.7.2", "unlazy-loader": "^0.1.3", "webpack": "^4.41.0", "webpack-cli": "^3.3.9", diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index 03eb048a125bb..ad9247523797a 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -58,7 +58,7 @@ export class Config { this[$values] = value; } - public has(key: string) { + public has(key: string | string[]) { function recursiveHasCheck( remainingPath: string[], values: Record, @@ -109,7 +109,7 @@ export class Config { return recursiveHasCheck(path, this[$values], schema); } - public get(key: string, defaultValue?: any) { + public get(key: string | string[], defaultValue?: any) { if (!this.has(key)) { throw new Error(`Unknown config key "${key}"`); } diff --git a/renovate.json5 b/renovate.json5 index 0c288bb85c72c..aefbc61e8dc12 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -513,6 +513,14 @@ '@types/podium', ], }, + { + groupSlug: '@reach/router', + groupName: '@reach/router related packages', + packageNames: [ + '@reach/router', + '@types/reach__router', + ], + }, { groupSlug: 'request', groupName: 'request related packages', diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 366a5b65fbb99..6989c2159dce3 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1130,7 +1130,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. | +| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. | | `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | | `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | | @@ -1142,6 +1142,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. | | `ui/registry/vis_types` | `visualizations.types` | -- | | `ui/vis` | `visualizations.types` | -- | +| `ui/share` | `share` | `showShareContextMenu` is now called `toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider` is now called `register` | | `ui/vis/vis_factory` | `visualizations.types` | -- | | `ui/vis/vis_filters` | `visualizations.filters` | -- | | `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 0088ef68aaeb8..7f9c75595a4ce 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -20,11 +20,12 @@ import { sortBy } from 'lodash'; import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; +import { MountPoint } from '../../types'; /** @public */ export interface ChromeNavControl { order?: number; - mount(targetDomElement: HTMLElement): () => void; + mount: MountPoint; } /** diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx index 90e7907b0068c..76413a0ea0317 100644 --- a/src/core/public/chrome/ui/header/header_extension.tsx +++ b/src/core/public/chrome/ui/header/header_extension.tsx @@ -18,9 +18,10 @@ */ import React from 'react'; +import { MountPoint } from '../../../types'; interface Props { - extension?: (el: HTMLDivElement) => () => void; + extension?: MountPoint; } export class HeaderExtension extends React.Component { diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 79fa32040b14c..22e315f9e1b03 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -19,7 +19,7 @@ import angular from 'angular'; import { InternalCoreSetup, InternalCoreStart } from '../core_system'; -import { LegacyCoreSetup, LegacyCoreStart } from '../'; +import { LegacyCoreSetup, LegacyCoreStart, MountPoint } from '../'; /** @internal */ export interface LegacyPlatformParams { @@ -40,7 +40,7 @@ interface StartDeps { } interface BootstrapModule { - bootstrap: (targetDomElement: HTMLElement) => void; + bootstrap: MountPoint; } /** diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index b9cd2577c2217..afd0825ec986c 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -117,9 +117,22 @@ function createCoreContext(): CoreContext { }; } +function createStorageMock() { + const storageMock: jest.Mocked = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn(), + key: jest.fn(), + length: 10, + }; + return storageMock; +} + export const coreMock = { createCoreContext, createSetup: createCoreSetupMock, createStart: createCoreStartMock, createPluginInitializerContext: pluginInitializerContextMock, + createStorage: createStorageMock, }; diff --git a/src/core/public/notifications/toasts/error_toast.test.tsx b/src/core/public/notifications/toasts/error_toast.test.tsx index b72b2de85340a..b497be526093d 100644 --- a/src/core/public/notifications/toasts/error_toast.test.tsx +++ b/src/core/public/notifications/toasts/error_toast.test.tsx @@ -40,6 +40,7 @@ function render(props: ErrorToastProps = {}) { error={props.error || new Error('error message')} title={props.title || 'An error occured'} toastMessage={props.toastMessage || 'This is the toast message'} + i18nContext={() => ({ children }) => {children}} /> ); } diff --git a/src/core/public/notifications/toasts/error_toast.tsx b/src/core/public/notifications/toasts/error_toast.tsx index 10bc51559644b..6b53719839b0f 100644 --- a/src/core/public/notifications/toasts/error_toast.tsx +++ b/src/core/public/notifications/toasts/error_toast.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; +import ReactDOM from 'react-dom'; import { EuiButton, @@ -32,12 +33,14 @@ import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { OverlayStart } from '../../overlays'; +import { I18nStart } from '../../i18n'; interface ErrorToastProps { title: string; error: Error; toastMessage: string; openModal: OverlayStart['openModal']; + i18nContext: () => I18nStart['Context']; } /** @@ -50,33 +53,48 @@ function showErrorDialog({ title, error, openModal, -}: Pick) { + i18nContext, +}: Pick) { + const I18nContext = i18nContext(); const modal = openModal( - - - {title} - - - - {error.stack && ( - - - - {error.stack} - - - )} - - - modal.close()} fill> - - - - + mount( + + + + {title} + + + + {error.stack && ( + + + + {error.stack} + + + )} + + + modal.close()} fill> + + + + + + ) ); } -export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToastProps) { +export function ErrorToast({ + title, + error, + toastMessage, + openModal, + i18nContext, +}: ErrorToastProps) { return (

{toastMessage}

@@ -84,7 +102,7 @@ export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToast showErrorDialog({ title, error, openModal })} + onClick={() => showErrorDialog({ title, error, openModal, i18nContext })} > ); } + +const mount = (component: React.ReactElement) => (container: HTMLElement) => { + ReactDOM.render(component, container); + return () => ReactDOM.unmountComponentAtNode(container); +}; diff --git a/src/core/public/notifications/toasts/toasts_api.test.ts b/src/core/public/notifications/toasts/toasts_api.test.ts index f99a28617aa5c..a0e419e989657 100644 --- a/src/core/public/notifications/toasts/toasts_api.test.ts +++ b/src/core/public/notifications/toasts/toasts_api.test.ts @@ -51,10 +51,13 @@ function uiSettingsMock() { function toastDeps() { return { uiSettings: uiSettingsMock(), - i18n: i18nServiceMock.createStartContract(), }; } +function startDeps() { + return { overlays: {} as any, i18n: i18nServiceMock.createStartContract() }; +} + describe('#get$()', () => { it('returns observable that emits NEW toast list when something added or removed', () => { const toasts = new ToastsApi(toastDeps()); @@ -188,6 +191,7 @@ describe('#addDanger()', () => { describe('#addError', () => { it('adds an error toast', async () => { const toasts = new ToastsApi(toastDeps()); + toasts.start(startDeps()); const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' }); expect(toast).toHaveProperty('color', 'danger'); expect(toast).toHaveProperty('title', 'Something went wrong'); @@ -195,6 +199,7 @@ describe('#addError', () => { it('returns the created toast', async () => { const toasts = new ToastsApi(toastDeps()); + toasts.start(startDeps()); const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' }); const currentToasts = await getCurrentToasts(toasts); expect(currentToasts[0]).toBe(toast); diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx index b49bafda5b26e..a21b727b02d73 100644 --- a/src/core/public/notifications/toasts/toasts_api.tsx +++ b/src/core/public/notifications/toasts/toasts_api.tsx @@ -26,6 +26,7 @@ import { MountPoint } from '../../types'; import { mountReactNode } from '../../utils'; import { UiSettingsClientContract } from '../../ui_settings'; import { OverlayStart } from '../../overlays'; +import { I18nStart } from '../../i18n'; /** * Allowed fields for {@link ToastInput}. @@ -96,14 +97,16 @@ export class ToastsApi implements IToasts { private uiSettings: UiSettingsClientContract; private overlays?: OverlayStart; + private i18n?: I18nStart; constructor(deps: { uiSettings: UiSettingsClientContract }) { this.uiSettings = deps.uiSettings; } /** @internal */ - public registerOverlays(overlays: OverlayStart) { + public start({ overlays, i18n }: { overlays: OverlayStart; i18n: I18nStart }) { this.overlays = overlays; + this.i18n = i18n; } /** Observable of the toast messages to show to the user. */ @@ -206,6 +209,7 @@ export class ToastsApi implements IToasts { error={error} title={options.title} toastMessage={message} + i18nContext={() => this.i18n!.Context} /> ), }); diff --git a/src/core/public/notifications/toasts/toasts_service.tsx b/src/core/public/notifications/toasts/toasts_service.tsx index 47d8c14f9af8b..81d23afc4f4d3 100644 --- a/src/core/public/notifications/toasts/toasts_service.tsx +++ b/src/core/public/notifications/toasts/toasts_service.tsx @@ -58,7 +58,7 @@ export class ToastsService { } public start({ i18n, overlays, targetDomElement }: StartDeps) { - this.api!.registerOverlays(overlays); + this.api!.start({ overlays, i18n }); this.targetDomElement = targetDomElement; render( diff --git a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap b/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap deleted file mode 100644 index 0c19c6312a672..0000000000000 --- a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = ` -Array [ - Array [ -
, - ], -] -`; - -exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = ` -Array [ - Array [ - - - - Flyout content - - - , -
, - ], -] -`; - -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` -Array [ - Array [ - - - - Flyout content 1 - - - , -
, - ], - Array [ - - - - Flyout content 2 - - - , -
, - ], -] -`; diff --git a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap b/src/core/public/overlays/__snapshots__/modal.test.tsx.snap deleted file mode 100644 index a4e6f5d6f72b8..0000000000000 --- a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap +++ /dev/null @@ -1,64 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = ` -Array [ - Array [ -
, - ], -] -`; - -exports[`ModalService openModal() renders a modal to the DOM 1`] = ` -Array [ - Array [ - - - - - Modal content - - - - , -
, - ], -] -`; - -exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` -Array [ - Array [ - - - - - Modal content 1 - - - - , -
, - ], - Array [ - - - - - Flyout content 2 - - - - , -
, - ], -] -`; diff --git a/src/core/public/overlays/_index.scss b/src/core/public/overlays/_index.scss index fe86883aedcf9..368dc9b644ff9 100644 --- a/src/core/public/overlays/_index.scss +++ b/src/core/public/overlays/_index.scss @@ -1 +1,2 @@ @import './banners/index'; +@import './mount_wrapper'; diff --git a/src/core/public/overlays/_mount_wrapper.scss b/src/core/public/overlays/_mount_wrapper.scss new file mode 100644 index 0000000000000..aafcc4bbe87db --- /dev/null +++ b/src/core/public/overlays/_mount_wrapper.scss @@ -0,0 +1,5 @@ +.kbnOverlayMountWrapper { + display: flex; + flex-direction: column; + height: 100%; +} diff --git a/src/core/public/overlays/banners/banners_service.tsx b/src/core/public/overlays/banners/banners_service.tsx index 31d49b5952e87..a26a7c71bc61f 100644 --- a/src/core/public/overlays/banners/banners_service.tsx +++ b/src/core/public/overlays/banners/banners_service.tsx @@ -97,9 +97,7 @@ export class OverlayBannersService { if (!banners$.value.has(id)) { return false; } - banners$.next(banners$.value.remove(id)); - return true; }, @@ -107,10 +105,8 @@ export class OverlayBannersService { if (!id || !banners$.value.has(id)) { return this.add(mount, priority); } - const nextId = genId(); const nextBanner = { id: nextId, mount, priority }; - banners$.next(banners$.value.remove(id).add(nextId, nextBanner)); return nextId; }, diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap new file mode 100644 index 0000000000000..94c11f0185427 --- /dev/null +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = ` +Array [ + Array [ +
, + ], +] +`; + +exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = ` +Array [ + Array [ + + + + + , +
, + ], +] +`; + +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; + +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` +Array [ + Array [ + + + + + , +
, + ], + Array [ + + + + + , +
, + ], +] +`; + +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; diff --git a/packages/kbn-es-query/src/es_query/index.d.ts b/src/core/public/overlays/flyout/flyout_service.mock.ts similarity index 56% rename from packages/kbn-es-query/src/es_query/index.d.ts rename to src/core/public/overlays/flyout/flyout_service.mock.ts index 9510a18441e53..91544500713d6 100644 --- a/packages/kbn-es-query/src/es_query/index.d.ts +++ b/src/core/public/overlays/flyout/flyout_service.mock.ts @@ -17,23 +17,27 @@ * under the License. */ -export function buildQueryFromFilters(filters: unknown[], indexPattern: unknown): unknown; -export function buildEsQuery( - indexPattern: unknown, - queries: unknown, - filters: unknown, - config?: { - allowLeadingWildcards: boolean; - queryStringOptions: unknown; - ignoreFilterIfFieldNotInIndex: boolean; - dateFormatTZ?: string | null; - } -): unknown; -export function getEsQueryConfig(config: { - get: (name: string) => unknown; -}): { - allowLeadingWildcards: boolean; - queryStringOptions: unknown; - ignoreFilterIfFieldNotInIndex: boolean; - dateFormatTZ?: string | null; +import { FlyoutService, OverlayFlyoutStart } from './flyout_service'; + +const createStartContractMock = () => { + const startContract: jest.Mocked = { + open: jest.fn().mockReturnValue({ + close: jest.fn(), + onClose: Promise.resolve(), + }), + }; + return startContract; +}; + +const createMock = () => { + const mocked: jest.Mocked> = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; +}; + +export const overlayFlyoutServiceMock = { + create: createMock, + createStartContract: createStartContractMock, }; diff --git a/src/core/public/overlays/flyout.test.tsx b/src/core/public/overlays/flyout/flyout_service.test.tsx similarity index 65% rename from src/core/public/overlays/flyout.test.tsx rename to src/core/public/overlays/flyout/flyout_service.test.tsx index afe37fc50776b..25ba94f577993 100644 --- a/src/core/public/overlays/flyout.test.tsx +++ b/src/core/public/overlays/flyout/flyout_service.test.tsx @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks'; +import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks'; -import React from 'react'; -import { i18nServiceMock } from '../i18n/i18n_service.mock'; -import { FlyoutRef, FlyoutService } from './flyout'; +import { mount } from 'enzyme'; +import { i18nServiceMock } from '../../i18n/i18n_service.mock'; +import { FlyoutService, OverlayFlyoutStart } from './flyout_service'; +import { OverlayRef } from '../types'; const i18nMock = i18nServiceMock.createStartContract(); @@ -29,35 +30,50 @@ beforeEach(() => { mockReactDomUnmount.mockClear(); }); +const mountText = (text: string) => (container: HTMLElement) => { + const content = document.createElement('span'); + content.textContent = text; + container.append(content); + return () => {}; +}; + +const getServiceStart = () => { + const service = new FlyoutService(); + return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') }); +}; + describe('FlyoutService', () => { + let flyouts: OverlayFlyoutStart; + beforeEach(() => { + flyouts = getServiceStart(); + }); + describe('openFlyout()', () => { it('renders a flyout to the DOM', () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); expect(mockReactDomRender).not.toHaveBeenCalled(); - flyoutService.openFlyout(i18nMock, Flyout content); + flyouts.open(mountText('Flyout content')); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); }); describe('with a currently active flyout', () => { - let target: HTMLElement; - let flyoutService: FlyoutService; - let ref1: FlyoutRef; + let ref1: OverlayRef; beforeEach(() => { - target = document.createElement('div'); - flyoutService = new FlyoutService(target); - ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1); + ref1 = flyouts.open(mountText('Flyout content')); }); it('replaces the current flyout with a new one', () => { - flyoutService.openFlyout(i18nMock, Flyout content 2); + flyouts.open(mountText('Flyout content 2')); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + const modalContent = mount(mockReactDomRender.mock.calls[1][0]); + expect(modalContent.html()).toMatchSnapshot(); expect(() => ref1.close()).not.toThrowError(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); it('resolves onClose on the previous ref', async () => { const onCloseComplete = jest.fn(); ref1.onClose.then(onCloseComplete); - flyoutService.openFlyout(i18nMock, Flyout content 2); + flyouts.open(mountText('Flyout content 2')); await ref1.onClose; expect(onCloseComplete).toBeCalledTimes(1); }); @@ -65,9 +81,7 @@ describe('FlyoutService', () => { }); describe('FlyoutRef#close()', () => { it('resolves the onClose Promise', async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref = flyoutService.openFlyout(i18nMock, Flyout content); + const ref = flyouts.open(mountText('Flyout content')); const onCloseComplete = jest.fn(); ref.onClose.then(onCloseComplete); @@ -76,9 +90,7 @@ describe('FlyoutService', () => { expect(onCloseComplete).toHaveBeenCalledTimes(1); }); it('can be called multiple times on the same FlyoutRef', async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref = flyoutService.openFlyout(i18nMock, Flyout content); + const ref = flyouts.open(mountText('Flyout content')); expect(mockReactDomUnmount).not.toHaveBeenCalled(); await ref.close(); expect(mockReactDomUnmount.mock.calls).toMatchSnapshot(); @@ -86,10 +98,8 @@ describe('FlyoutService', () => { expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); it("on a stale FlyoutRef doesn't affect the active flyout", async () => { - const target = document.createElement('div'); - const flyoutService = new FlyoutService(target); - const ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1); - const ref2 = flyoutService.openFlyout(i18nMock, Flyout content 2); + const ref1 = flyouts.open(mountText('Flyout content 1')); + const ref2 = flyouts.open(mountText('Flyout content 2')); const onCloseComplete = jest.fn(); ref2.onClose.then(onCloseComplete); mockReactDomUnmount.mockClear(); diff --git a/src/core/public/overlays/flyout.tsx b/src/core/public/overlays/flyout/flyout_service.tsx similarity index 57% rename from src/core/public/overlays/flyout.tsx rename to src/core/public/overlays/flyout/flyout_service.tsx index c8ba9c6b284d3..b609b2ce1d741 100644 --- a/src/core/public/overlays/flyout.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -23,8 +23,10 @@ import { EuiFlyout } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; -import { I18nStart } from '../i18n'; -import { OverlayRef } from './overlay_service'; +import { I18nStart } from '../../i18n'; +import { MountPoint } from '../../types'; +import { OverlayRef } from '../types'; +import { MountWrapper } from '../../utils'; /** * A FlyoutRef is a reference to an opened flyout panel. It offers methods to @@ -37,7 +39,7 @@ import { OverlayRef } from './overlay_service'; * * @public */ -export class FlyoutRef implements OverlayRef { +class FlyoutRef implements OverlayRef { /** * An Promise that will resolve once this flyout is closed. * @@ -66,55 +68,77 @@ export class FlyoutRef implements OverlayRef { } } +/** + * APIs to open and manage fly-out dialogs. + * + * @public + */ +export interface OverlayFlyoutStart { + /** + * Opens a flyout panel with the given mount point inside. You can use + * `close()` on the returned FlyoutRef to close the flyout. + * + * @param mount {@link MountPoint} - Mounts the children inside a flyout panel + * @param options {@link OverlayFlyoutOpenOptions} - options for the flyout + * @return {@link OverlayRef} A reference to the opened flyout panel. + */ + open(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef; +} + +/** + * @public + */ +export interface OverlayFlyoutOpenOptions { + className?: string; + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; +} + +interface StartDeps { + i18n: I18nStart; + targetDomElement: Element; +} + /** @internal */ export class FlyoutService { private activeFlyout: FlyoutRef | null = null; + private targetDomElement: Element | null = null; - constructor(private readonly targetDomElement: Element) {} + public start({ i18n, targetDomElement }: StartDeps): OverlayFlyoutStart { + this.targetDomElement = targetDomElement; - /** - * Opens a flyout panel with the given component inside. You can use - * `close()` on the returned FlyoutRef to close the flyout. - * - * @param flyoutChildren - Mounts the children inside a flyout panel - * @return {FlyoutRef} A reference to the opened flyout panel. - */ - public openFlyout = ( - i18n: I18nStart, - flyoutChildren: React.ReactNode, - flyoutProps: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } = {} - ): FlyoutRef => { - // If there is an active flyout session close it before opening a new one. - if (this.activeFlyout) { - this.activeFlyout.close(); - this.cleanupDom(); - } + return { + open: (mount: MountPoint, options: OverlayFlyoutOpenOptions = {}): OverlayRef => { + // If there is an active flyout session close it before opening a new one. + if (this.activeFlyout) { + this.activeFlyout.close(); + this.cleanupDom(); + } - const flyout = new FlyoutRef(); + const flyout = new FlyoutRef(); - // If a flyout gets closed through it's FlyoutRef, remove it from the dom - flyout.onClose.then(() => { - if (this.activeFlyout === flyout) { - this.cleanupDom(); - } - }); + // If a flyout gets closed through it's FlyoutRef, remove it from the dom + flyout.onClose.then(() => { + if (this.activeFlyout === flyout) { + this.cleanupDom(); + } + }); - this.activeFlyout = flyout; + this.activeFlyout = flyout; - render( - - flyout.close()}> - {flyoutChildren} - - , - this.targetDomElement - ); + render( + + flyout.close()}> + + + , + this.targetDomElement + ); - return flyout; - }; + return flyout; + }, + }; + } /** * Using React.Render to re-render into a target DOM element will replace @@ -124,8 +148,10 @@ export class FlyoutService { * depend on unmounting for cleanup behaviour. */ private cleanupDom(): void { - unmountComponentAtNode(this.targetDomElement); - this.targetDomElement.innerHTML = ''; + if (this.targetDomElement != null) { + unmountComponentAtNode(this.targetDomElement); + this.targetDomElement.innerHTML = ''; + } this.activeFlyout = null; } } diff --git a/src/core/public/overlays/flyout/index.ts b/src/core/public/overlays/flyout/index.ts new file mode 100644 index 0000000000000..b01cc3af5fa38 --- /dev/null +++ b/src/core/public/overlays/flyout/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { FlyoutService, OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout_service'; diff --git a/src/core/public/overlays/index.ts b/src/core/public/overlays/index.ts index ff03e5dffb2ca..417486f78f719 100644 --- a/src/core/public/overlays/index.ts +++ b/src/core/public/overlays/index.ts @@ -17,5 +17,8 @@ * under the License. */ +export { OverlayRef } from './types'; export { OverlayBannersStart } from './banners'; -export { OverlayService, OverlayStart, OverlayRef } from './overlay_service'; +export { OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout'; +export { OverlayModalStart, OverlayModalOpenOptions } from './modal'; +export { OverlayService, OverlayStart } from './overlay_service'; diff --git a/src/core/public/overlays/modal.tsx b/src/core/public/overlays/modal.tsx deleted file mode 100644 index 6f94788b84d71..0000000000000 --- a/src/core/public/overlays/modal.tsx +++ /dev/null @@ -1,122 +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. - */ - -/* eslint-disable max-classes-per-file */ - -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { Subject } from 'rxjs'; -import { I18nStart } from '../i18n'; -import { OverlayRef } from './overlay_service'; - -/** - * A ModalRef is a reference to an opened modal. It offers methods to - * close the modal. - * - * @public - */ -export class ModalRef implements OverlayRef { - public readonly onClose: Promise; - - private closeSubject = new Subject(); - - constructor() { - this.onClose = this.closeSubject.toPromise(); - } - - /** - * Closes the referenced modal if it's still open which in turn will - * resolve the `onClose` Promise. If the modal had already been - * closed this method does nothing. - */ - public close(): Promise { - if (!this.closeSubject.closed) { - this.closeSubject.next(); - this.closeSubject.complete(); - } - return this.onClose; - } -} - -/** @internal */ -export class ModalService { - private activeModal: ModalRef | null = null; - - constructor(private readonly targetDomElement: Element) {} - - /** - * Opens a flyout panel with the given component inside. You can use - * `close()` on the returned FlyoutRef to close the flyout. - * - * @param flyoutChildren - Mounts the children inside a flyout panel - * @return {FlyoutRef} A reference to the opened flyout panel. - */ - public openModal = ( - i18n: I18nStart, - modalChildren: React.ReactNode, - modalProps: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } = {} - ): ModalRef => { - // If there is an active flyout session close it before opening a new one. - if (this.activeModal) { - this.activeModal.close(); - this.cleanupDom(); - } - - const modal = new ModalRef(); - - // If a modal gets closed through it's ModalRef, remove it from the dom - modal.onClose.then(() => { - if (this.activeModal === modal) { - this.cleanupDom(); - } - }); - - this.activeModal = modal; - - render( - - - modal.close()}> - {modalChildren} - - - , - this.targetDomElement - ); - - return modal; - }; - - /** - * Using React.Render to re-render into a target DOM element will replace - * the content of the target but won't call unmountComponent on any - * components inside the target or any of their children. So we properly - * cleanup the DOM here to prevent subtle bugs in child components which - * depend on unmounting for cleanup behaviour. - */ - private cleanupDom(): void { - unmountComponentAtNode(this.targetDomElement); - this.targetDomElement.innerHTML = ''; - this.activeModal = null; - } -} diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap new file mode 100644 index 0000000000000..3928c54f90179 --- /dev/null +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = ` +Array [ + Array [ +
, + ], +] +`; + +exports[`ModalService openModal() renders a modal to the DOM 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], +] +`; + +exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; + +exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], + Array [ + + + + + + + , +
, + ], +] +`; diff --git a/src/core/public/overlays/modal/index.ts b/src/core/public/overlays/modal/index.ts new file mode 100644 index 0000000000000..9ef4492af3a3a --- /dev/null +++ b/src/core/public/overlays/modal/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { ModalService, OverlayModalStart, OverlayModalOpenOptions } from './modal_service'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts b/src/core/public/overlays/modal/modal_service.mock.ts similarity index 56% rename from src/legacy/core_plugins/expressions/public/np_ready/public/types.ts rename to src/core/public/overlays/modal/modal_service.mock.ts index 9d7b4fb6d0480..726209b8f277c 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts +++ b/src/core/public/overlays/modal/modal_service.mock.ts @@ -17,9 +17,27 @@ * under the License. */ -import { TimeRange } from '../../../../../../plugins/data/public'; -import { Adapters } from '../../../../../../plugins/inspector/public'; -import { Query } from '../../../../../../plugins/data/public'; +import { ModalService, OverlayModalStart } from './modal_service'; -export { TimeRange, Adapters, Query }; -export * from '../../../../../../plugins/expressions/public'; +const createStartContractMock = () => { + const startContract: jest.Mocked = { + open: jest.fn().mockReturnValue({ + close: jest.fn(), + onClose: Promise.resolve(), + }), + }; + return startContract; +}; + +const createMock = () => { + const mocked: jest.Mocked> = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; +}; + +export const overlayModalServiceMock = { + create: createMock, + createStartContract: createStartContractMock, +}; diff --git a/src/core/public/overlays/modal.test.tsx b/src/core/public/overlays/modal/modal_service.test.tsx similarity index 65% rename from src/core/public/overlays/modal.test.tsx rename to src/core/public/overlays/modal/modal_service.test.tsx index ee8bba5d61112..582c2697aef30 100644 --- a/src/core/public/overlays/modal.test.tsx +++ b/src/core/public/overlays/modal/modal_service.test.tsx @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks'; +import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks'; import React from 'react'; -import { i18nServiceMock } from '../i18n/i18n_service.mock'; -import { ModalService, ModalRef } from './modal'; +import { mount } from 'enzyme'; +import { i18nServiceMock } from '../../i18n/i18n_service.mock'; +import { ModalService, OverlayModalStart } from './modal_service'; +import { mountReactNode } from '../../utils'; +import { OverlayRef } from '../types'; const i18nMock = i18nServiceMock.createStartContract(); @@ -29,45 +32,59 @@ beforeEach(() => { mockReactDomUnmount.mockClear(); }); +const getServiceStart = () => { + const service = new ModalService(); + return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') }); +}; + describe('ModalService', () => { + let modals: OverlayModalStart; + beforeEach(() => { + modals = getServiceStart(); + }); + describe('openModal()', () => { it('renders a modal to the DOM', () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); expect(mockReactDomRender).not.toHaveBeenCalled(); - modalService.openModal(i18nMock, Modal content); + modals.open(container => { + const content = document.createElement('span'); + content.textContent = 'Modal content'; + container.append(content); + return () => {}; + }); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); }); + describe('with a currently active modal', () => { - let target: HTMLElement; - let modalService: ModalService; - let ref1: ModalRef; + let ref1: OverlayRef; + beforeEach(() => { - target = document.createElement('div'); - modalService = new ModalService(target); - ref1 = modalService.openModal(i18nMock, Modal content 1); + ref1 = modals.open(mountReactNode(Modal content 1)); }); + it('replaces the current modal with a new one', () => { - modalService.openModal(i18nMock, Flyout content 2); + modals.open(mountReactNode(Flyout content 2)); expect(mockReactDomRender.mock.calls).toMatchSnapshot(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); expect(() => ref1.close()).not.toThrowError(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); + it('resolves onClose on the previous ref', async () => { const onCloseComplete = jest.fn(); ref1.onClose.then(onCloseComplete); - modalService.openModal(i18nMock, Flyout content 2); + modals.open(mountReactNode(Flyout content 2)); await ref1.onClose; expect(onCloseComplete).toBeCalledTimes(1); }); }); }); + describe('ModalRef#close()', () => { it('resolves the onClose Promise', async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref = modalService.openModal(i18nMock, Flyout content); + const ref = modals.open(mountReactNode(Flyout content)); const onCloseComplete = jest.fn(); ref.onClose.then(onCloseComplete); @@ -75,21 +92,19 @@ describe('ModalService', () => { await ref.close(); expect(onCloseComplete).toHaveBeenCalledTimes(1); }); + it('can be called multiple times on the same ModalRef', async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref = modalService.openModal(i18nMock, Flyout content); + const ref = modals.open(mountReactNode(Flyout content)); expect(mockReactDomUnmount).not.toHaveBeenCalled(); await ref.close(); expect(mockReactDomUnmount.mock.calls).toMatchSnapshot(); await ref.close(); expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); }); + it("on a stale ModalRef doesn't affect the active flyout", async () => { - const target = document.createElement('div'); - const modalService = new ModalService(target); - const ref1 = modalService.openModal(i18nMock, Modal content 1); - const ref2 = modalService.openModal(i18nMock, Modal content 2); + const ref1 = modals.open(mountReactNode(Modal content 1)); + const ref2 = modals.open(mountReactNode(Modal content 2)); const onCloseComplete = jest.fn(); ref2.onClose.then(onCloseComplete); mockReactDomUnmount.mockClear(); diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx new file mode 100644 index 0000000000000..cb77c2ec4c88c --- /dev/null +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -0,0 +1,148 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Subject } from 'rxjs'; +import { I18nStart } from '../../i18n'; +import { MountPoint } from '../../types'; +import { OverlayRef } from '../types'; +import { MountWrapper } from '../../utils'; + +/** + * A ModalRef is a reference to an opened modal. It offers methods to + * close the modal. + * + * @public + */ +class ModalRef implements OverlayRef { + public readonly onClose: Promise; + + private closeSubject = new Subject(); + + constructor() { + this.onClose = this.closeSubject.toPromise(); + } + + /** + * Closes the referenced modal if it's still open which in turn will + * resolve the `onClose` Promise. If the modal had already been + * closed this method does nothing. + */ + public close(): Promise { + if (!this.closeSubject.closed) { + this.closeSubject.next(); + this.closeSubject.complete(); + } + return this.onClose; + } +} + +/** + * APIs to open and manage modal dialogs. + * + * @public + */ +export interface OverlayModalStart { + /** + * Opens a modal panel with the given mount point inside. You can use + * `close()` on the returned OverlayRef to close the modal. + * + * @param mount {@link MountPoint} - Mounts the children inside the modal + * @param options {@link OverlayModalOpenOptions} - options for the modal + * @return {@link OverlayRef} A reference to the opened modal. + */ + open(mount: MountPoint, options?: OverlayModalOpenOptions): OverlayRef; +} + +/** + * @public + */ +export interface OverlayModalOpenOptions { + className?: string; + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; +} + +interface StartDeps { + i18n: I18nStart; + targetDomElement: Element; +} + +/** @internal */ +export class ModalService { + private activeModal: ModalRef | null = null; + private targetDomElement: Element | null = null; + + public start({ i18n, targetDomElement }: StartDeps): OverlayModalStart { + this.targetDomElement = targetDomElement; + + return { + open: (mount: MountPoint, options: OverlayModalOpenOptions = {}): OverlayRef => { + // If there is an active flyout session close it before opening a new one. + if (this.activeModal) { + this.activeModal.close(); + this.cleanupDom(); + } + + const modal = new ModalRef(); + + // If a modal gets closed through it's ModalRef, remove it from the dom + modal.onClose.then(() => { + if (this.activeModal === modal) { + this.cleanupDom(); + } + }); + + this.activeModal = modal; + + render( + + + modal.close()}> + + + + , + targetDomElement + ); + + return modal; + }, + }; + } + + /** + * Using React.Render to re-render into a target DOM element will replace + * the content of the target but won't call unmountComponent on any + * components inside the target or any of their children. So we properly + * cleanup the DOM here to prevent subtle bugs in child components which + * depend on unmounting for cleanup behaviour. + */ + private cleanupDom(): void { + if (this.targetDomElement != null) { + unmountComponentAtNode(this.targetDomElement); + this.targetDomElement.innerHTML = ''; + } + this.activeModal = null; + } +} diff --git a/src/core/public/overlays/flyout.test.mocks.ts b/src/core/public/overlays/overlay.test.mocks.ts similarity index 88% rename from src/core/public/overlays/flyout.test.mocks.ts rename to src/core/public/overlays/overlay.test.mocks.ts index 35a046e960077..563f414a0ae99 100644 --- a/src/core/public/overlays/flyout.test.mocks.ts +++ b/src/core/public/overlays/overlay.test.mocks.ts @@ -19,7 +19,9 @@ export const mockReactDomRender = jest.fn(); export const mockReactDomUnmount = jest.fn(); +export const mockReactDomCreatePortal = jest.fn().mockImplementation(component => component); jest.doMock('react-dom', () => ({ render: mockReactDomRender, + createPortal: mockReactDomCreatePortal, unmountComponentAtNode: mockReactDomUnmount, })); diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts index 39abc6f765b97..2937ec89bfc74 100644 --- a/src/core/public/overlays/overlay_service.mock.ts +++ b/src/core/public/overlays/overlay_service.mock.ts @@ -18,17 +18,15 @@ */ import { OverlayService, OverlayStart } from './overlay_service'; import { overlayBannersServiceMock } from './banners/banners_service.mock'; +import { overlayFlyoutServiceMock } from './flyout/flyout_service.mock'; +import { overlayModalServiceMock } from './modal/modal_service.mock'; const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { - openFlyout: jest.fn(), - openModal: jest.fn(), + openFlyout: overlayFlyoutServiceMock.createStartContract().open, + openModal: overlayModalServiceMock.createStartContract().open, banners: overlayBannersServiceMock.createStartContract(), }; - startContract.openModal.mockReturnValue({ - close: jest.fn(), - onClose: Promise.resolve(), - }); return startContract; }; diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts index 1a72bb5dbe435..82fe753d6f283 100644 --- a/src/core/public/overlays/overlay_service.ts +++ b/src/core/public/overlays/overlay_service.ts @@ -17,34 +17,11 @@ * under the License. */ -import React from 'react'; - -import { FlyoutService } from './flyout'; -import { ModalService } from './modal'; import { I18nStart } from '../i18n'; -import { OverlayBannersStart, OverlayBannersService } from './banners'; import { UiSettingsClientContract } from '../ui_settings'; - -/** - * Returned by {@link OverlayStart} methods for closing a mounted overlay. - * @public - */ -export interface OverlayRef { - /** - * A Promise that will resolve once this overlay is closed. - * - * Overlays can close from user interaction, calling `close()` on the overlay - * reference or another overlay replacing yours via `openModal` or `openFlyout`. - */ - onClose: Promise; - - /** - * Closes the referenced overlay if it's still open which in turn will - * resolve the `onClose` Promise. If the overlay had already been - * closed this method does nothing. - */ - close(): Promise; -} +import { OverlayBannersStart, OverlayBannersService } from './banners'; +import { FlyoutService, OverlayFlyoutStart } from './flyout'; +import { ModalService, OverlayModalStart } from './modal'; interface StartDeps { i18n: I18nStart; @@ -54,19 +31,25 @@ interface StartDeps { /** @internal */ export class OverlayService { + private bannersService = new OverlayBannersService(); + private modalService = new ModalService(); + private flyoutService = new FlyoutService(); + public start({ i18n, targetDomElement, uiSettings }: StartDeps): OverlayStart { const flyoutElement = document.createElement('div'); - const modalElement = document.createElement('div'); targetDomElement.appendChild(flyoutElement); + const flyouts = this.flyoutService.start({ i18n, targetDomElement: flyoutElement }); + + const banners = this.bannersService.start({ i18n, uiSettings }); + + const modalElement = document.createElement('div'); targetDomElement.appendChild(modalElement); - const flyoutService = new FlyoutService(flyoutElement); - const modalService = new ModalService(modalElement); - const bannersService = new OverlayBannersService(); + const modals = this.modalService.start({ i18n, targetDomElement: modalElement }); return { - banners: bannersService.start({ i18n, uiSettings }), - openFlyout: flyoutService.openFlyout.bind(flyoutService, i18n), - openModal: modalService.openModal.bind(modalService, i18n), + banners, + openFlyout: flyouts.open.bind(flyouts), + openModal: modals.open.bind(modals), }; } } @@ -75,19 +58,8 @@ export class OverlayService { export interface OverlayStart { /** {@link OverlayBannersStart} */ banners: OverlayBannersStart; - openFlyout: ( - flyoutChildren: React.ReactNode, - flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } - ) => OverlayRef; - openModal: ( - modalChildren: React.ReactNode, - modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - } - ) => OverlayRef; + /** {@link OverlayFlyoutStart#open} */ + openFlyout: OverlayFlyoutStart['open']; + /** {@link OverlayModalStart#open} */ + openModal: OverlayModalStart['open']; } diff --git a/packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js b/src/core/public/overlays/types.ts similarity index 56% rename from packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js rename to src/core/public/overlays/types.ts index deaf258418ed4..d5bd01c672d1f 100644 --- a/packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js +++ b/src/core/public/overlays/types.ts @@ -17,15 +17,23 @@ * under the License. */ -import _ from 'lodash'; -import { nodeTypes } from '../node_types'; - -export function convertGeoBoundingBox(filter) { - if (filter.meta.type !== 'geo_bounding_box') { - throw new Error(`Expected filter of type "geo_bounding_box", got "${filter.meta.type}"`); - } +/** + * Returned by {@link OverlayStart} methods for closing a mounted overlay. + * @public + */ +export interface OverlayRef { + /** + * A Promise that will resolve once this overlay is closed. + * + * Overlays can close from user interaction, calling `close()` on the overlay + * reference or another overlay replacing yours via `openModal` or `openFlyout`. + */ + onClose: Promise; - const { key, params } = filter.meta; - const camelParams = _.mapKeys(params, (value, key) => _.camelCase(key)); - return nodeTypes.function.buildNode('geoBoundingBox', key, camelParams); + /** + * Closes the referenced overlay if it's still open which in turn will + * resolve the `onClose` Promise. If the overlay had already been + * closed this method does nothing. + */ + close(): Promise; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1e97d8e066d09..7abbbcd32fbb8 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -124,7 +124,7 @@ export type ChromeHelpExtension = (element: HTMLDivElement) => () => void; // @public (undocumented) export interface ChromeNavControl { // (undocumented) - mount(targetDomElement: HTMLElement): () => void; + mount: MountPoint; // (undocumented) order?: number; } @@ -620,7 +620,7 @@ export interface LegacyNavLink { } // @public -export type MountPoint = (element: HTMLElement) => UnmountCallback; +export type MountPoint = (element: T) => UnmountCallback; // @public (undocumented) export interface NotificationsSetup { @@ -657,17 +657,14 @@ export interface OverlayRef { export interface OverlayStart { // (undocumented) banners: OverlayBannersStart; + // Warning: (ae-forgotten-export) The symbol "OverlayFlyoutStart" needs to be exported by the entry point index.d.ts + // // (undocumented) - openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: { - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; + openFlyout: OverlayFlyoutStart['open']; + // Warning: (ae-forgotten-export) The symbol "OverlayModalStart" needs to be exported by the entry point index.d.ts + // // (undocumented) - openModal: (modalChildren: React.ReactNode, modalProps?: { - className?: string; - closeButtonAriaLabel?: string; - 'data-test-subj'?: string; - }) => OverlayRef; + openModal: OverlayModalStart['open']; } // @public (undocumented) @@ -941,9 +938,12 @@ export class ToastsApi implements IToasts { addSuccess(toastOrTitle: ToastInput): Toast; addWarning(toastOrTitle: ToastInput): Toast; get$(): Rx.Observable; - // @internal (undocumented) - registerOverlays(overlays: OverlayStart): void; remove(toastOrId: Toast | string): void; + // @internal (undocumented) + start({ overlays, i18n }: { + overlays: OverlayStart; + i18n: I18nStart; + }): void; } // @public (undocumented) diff --git a/src/core/public/types.ts b/src/core/public/types.ts index 4b12d5bc6da51..5abbb3c55813a 100644 --- a/src/core/public/types.ts +++ b/src/core/public/types.ts @@ -26,7 +26,7 @@ * * @public */ -export type MountPoint = (element: HTMLElement) => UnmountCallback; +export type MountPoint = (element: T) => UnmountCallback; /** * A function that will unmount the element previously mounted by diff --git a/src/core/public/utils/mount.test.tsx b/src/core/public/utils/mount.test.tsx new file mode 100644 index 0000000000000..1cacb7d6a796c --- /dev/null +++ b/src/core/public/utils/mount.test.tsx @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { MountWrapper, mountReactNode } from './mount'; + +describe('MountWrapper', () => { + it('renders an html element in react tree', () => { + const mountPoint = (container: HTMLElement) => { + const el = document.createElement('p'); + el.textContent = 'hello'; + el.className = 'bar'; + container.append(el); + return () => {}; + }; + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"

hello

"` + ); + }); + + it('updates the react tree when the mounted element changes', () => { + const el = document.createElement('p'); + el.textContent = 'initial'; + + const mountPoint = (container: HTMLElement) => { + container.append(el); + return () => {}; + }; + + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"

initial

"` + ); + + el.textContent = 'changed'; + container.update(); + expect(container.html()).toMatchInlineSnapshot( + `"

changed

"` + ); + }); + + it('can render a detached react component', () => { + const mountPoint = mountReactNode(detached); + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"
detached
"` + ); + }); + + it('accepts a className prop to override default className', () => { + const mountPoint = mountReactNode(detached); + const wrapper = ; + const container = mount(wrapper); + expect(container.html()).toMatchInlineSnapshot( + `"
detached
"` + ); + }); +}); diff --git a/src/core/public/utils/mount.tsx b/src/core/public/utils/mount.tsx index dbd7d5da435a6..0fee67a6e7fbc 100644 --- a/src/core/public/utils/mount.tsx +++ b/src/core/public/utils/mount.tsx @@ -22,23 +22,26 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { MountPoint } from '../types'; +const defaultWrapperClass = 'kbnMountWrapper'; + /** * MountWrapper is a react component to mount a {@link MountPoint} inside a react tree. */ -export const MountWrapper: React.FunctionComponent<{ mount: MountPoint }> = ({ mount }) => { +export const MountWrapper: React.FunctionComponent<{ mount: MountPoint; className?: string }> = ({ + mount, + className = defaultWrapperClass, +}) => { const element = useRef(null); useEffect(() => mount(element.current!), [mount]); - return
; + return
; }; /** - * Mount converter for react components. + * Mount converter for react node. * - * @param component to get a mount for + * @param node to get a mount for */ -export const mountReactNode = (component: React.ReactNode): MountPoint => ( - element: HTMLElement -) => { - render({component}, element); +export const mountReactNode = (node: React.ReactNode): MountPoint => (element: HTMLElement) => { + render({node}, element); return () => unmountComponentAtNode(element); }; diff --git a/src/core/server/config/config_service.test.ts b/src/core/server/config/config_service.test.ts index 61da9af7baa7c..131e1dd501792 100644 --- a/src/core/server/config/config_service.test.ts +++ b/src/core/server/config/config_service.test.ts @@ -55,7 +55,7 @@ test('throws if config at path does not match schema', async () => { await expect( configService.setSchema('key', schema.string()) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"[key]: expected value of type [string] but got [number]"` + `"[config validation of [key]]: expected value of type [string] but got [number]"` ); }); @@ -78,11 +78,11 @@ test('re-validate config when updated', async () => { config$.next(new ObjectToConfigAdapter({ key: 123 })); await expect(valuesReceived).toMatchInlineSnapshot(` -Array [ - "value", - [Error: [key]: expected value of type [string] but got [number]], -] -`); + Array [ + "value", + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] + `); }); test("returns undefined if fetching optional config at a path that doesn't exist", async () => { @@ -143,7 +143,7 @@ test("throws error if 'schema' is not defined for a key", async () => { const configs = configService.atPath('key'); await expect(configs.pipe(first()).toPromise()).rejects.toMatchInlineSnapshot( - `[Error: No validation schema has been defined for key]` + `[Error: No validation schema has been defined for [key]]` ); }); @@ -153,7 +153,7 @@ test("throws error if 'setSchema' called several times for the same key", async const addSchema = async () => await configService.setSchema('key', schema.string()); await addSchema(); await expect(addSchema()).rejects.toMatchInlineSnapshot( - `[Error: Validation schema for key was already registered.]` + `[Error: Validation schema for [key] was already registered.]` ); }); @@ -280,6 +280,33 @@ test('handles disabled path and marks config as used', async () => { expect(unusedPaths).toEqual([]); }); +test('does not throw if schema does not define "enabled" schema', async () => { + const initialConfig = { + pid: { + file: '/some/file.pid', + }, + }; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + expect( + configService.setSchema( + 'pid', + schema.object({ + file: schema.string(), + }) + ) + ).resolves.toBeUndefined(); + + const value$ = configService.atPath('pid'); + const value: any = await value$.pipe(first()).toPromise(); + expect(value.enabled).toBe(undefined); + + const valueOptional$ = configService.optionalAtPath('pid'); + const valueOptional: any = await valueOptional$.pipe(first()).toPromise(); + expect(valueOptional.enabled).toBe(undefined); +}); + test('treats config as enabled if config path is not present in config', async () => { const initialConfig = {}; @@ -292,3 +319,45 @@ test('treats config as enabled if config path is not present in config', async ( const unusedPaths = await configService.getUnusedPaths(); expect(unusedPaths).toEqual([]); }); + +test('read "enabled" even if its schema is not present', async () => { + const initialConfig = { + foo: { + enabled: true, + }, + }; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + + const isEnabled = await configService.isEnabledAtPath('foo'); + expect(isEnabled).toBe(true); +}); + +test('allows plugins to specify "enabled" flag via validation schema', async () => { + const initialConfig = {}; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + + await configService.setSchema( + 'foo', + schema.object({ enabled: schema.boolean({ defaultValue: false }) }) + ); + + expect(await configService.isEnabledAtPath('foo')).toBe(false); + + await configService.setSchema( + 'bar', + schema.object({ enabled: schema.boolean({ defaultValue: true }) }) + ); + + expect(await configService.isEnabledAtPath('bar')).toBe(true); + + await configService.setSchema( + 'baz', + schema.object({ different: schema.boolean({ defaultValue: true }) }) + ); + + expect(await configService.isEnabledAtPath('baz')).toBe(true); +}); diff --git a/src/core/server/config/config_service.ts b/src/core/server/config/config_service.ts index 8d3cc733cf250..c18a5b2000e01 100644 --- a/src/core/server/config/config_service.ts +++ b/src/core/server/config/config_service.ts @@ -54,7 +54,7 @@ export class ConfigService { public async setSchema(path: ConfigPath, schema: Type) { const namespace = pathToString(path); if (this.schemas.has(namespace)) { - throw new Error(`Validation schema for ${path} was already registered.`); + throw new Error(`Validation schema for [${path}] was already registered.`); } this.schemas.set(namespace, schema); @@ -98,14 +98,28 @@ export class ConfigService { } public async isEnabledAtPath(path: ConfigPath) { - const enabledPath = createPluginEnabledPath(path); + const namespace = pathToString(path); + + const validatedConfig = this.schemas.has(namespace) + ? await this.atPath<{ enabled?: boolean }>(path) + .pipe(first()) + .toPromise() + : undefined; + const enabledPath = createPluginEnabledPath(path); const config = await this.config$.pipe(first()).toPromise(); - if (!config.has(enabledPath)) { + + // if plugin hasn't got a config schema, we try to read "enabled" directly + const isEnabled = + validatedConfig && validatedConfig.enabled !== undefined + ? validatedConfig.enabled + : config.get(enabledPath); + + // not declared. consider that plugin is enabled by default + if (isEnabled === undefined) { return true; } - const isEnabled = config.get(enabledPath); if (isEnabled === false) { // If the plugin is _not_ enabled, we mark the entire plugin path as // handled, as it's expected that it won't be used. @@ -138,7 +152,7 @@ export class ConfigService { const namespace = pathToString(path); const schema = this.schemas.get(namespace); if (!schema) { - throw new Error(`No validation schema has been defined for ${namespace}`); + throw new Error(`No validation schema has been defined for [${namespace}]`); } return schema.validate( config, @@ -147,7 +161,7 @@ export class ConfigService { prod: this.env.mode.prod, ...this.env.packageInfo, }, - namespace + `config validation of [${namespace}]` ); } diff --git a/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap b/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap new file mode 100644 index 0000000000000..e81336c8863f5 --- /dev/null +++ b/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#username throws if equal to "elastic", only while running from source 1`] = `"[username]: value of \\"elastic\\" is forbidden. This is a superuser account that can obfuscate privilege-related issues. You should use the \\"kibana\\" user instead."`; diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 383ba77f17779..5a52e1ea51ccc 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -107,3 +107,11 @@ test('#ssl.certificateAuthorities accepts both string and array of strings', () ); expect(configValue.ssl.certificateAuthorities).toEqual(['some-path', 'another-path']); }); + +test('#username throws if equal to "elastic", only while running from source', () => { + const obj = { + username: 'elastic', + }; + expect(() => config.schema.validate(obj, { dist: false })).toThrowErrorMatchingSnapshot(); + expect(() => config.schema.validate(obj, { dist: true })).not.toThrow(); +}); diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index 947a0d27546b1..23a1c69d055bc 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -19,6 +19,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Duration } from 'moment'; +import { Logger } from '../logging'; const hostURISchema = schema.uri({ scheme: ['http', 'https'] }); @@ -39,7 +40,23 @@ export const config = { defaultValue: 'http://localhost:9200', }), preserveHost: schema.boolean({ defaultValue: true }), - username: schema.maybe(schema.string()), + username: schema.maybe( + schema.conditional( + schema.contextRef('dist'), + false, + schema.string({ + validate: rawConfig => { + if (rawConfig === 'elastic') { + return ( + 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + + 'privilege-related issues. You should use the "kibana" user instead.' + ); + } + }, + }), + schema.string() + ) + ), password: schema.maybe(schema.string()), requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { defaultValue: ['authorization'], @@ -166,7 +183,7 @@ export class ElasticsearchConfig { */ public readonly customHeaders: ElasticsearchConfigType['customHeaders']; - constructor(rawConfig: ElasticsearchConfigType) { + constructor(rawConfig: ElasticsearchConfigType, log?: Logger) { this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch; this.apiVersion = rawConfig.apiVersion; this.logQueries = rawConfig.logQueries; @@ -195,5 +212,14 @@ export class ElasticsearchConfig { ...rawConfig.ssl, certificateAuthorities, }; + + if (this.username === 'elastic' && log !== undefined) { + // logger is optional / not used during tests + // TODO: logger can be removed when issue #40255 is resolved to support deprecations in NP config service + log.warn( + `Setting the elasticsearch username to "elastic" is deprecated. You should use the "kibana" user instead.`, + { tags: ['deprecation'] } + ); + } } } diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 1f062412edaf2..be0a817c54146 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -51,7 +51,7 @@ export class ElasticsearchService implements CoreService('elasticsearch') - .pipe(map(rawConfig => new ElasticsearchConfig(rawConfig))); + .pipe(map(rawConfig => new ElasticsearchConfig(rawConfig, coreContext.logger.get('config')))); } public async setup(deps: SetupDeps): Promise { diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts index ff7fee0198f68..cde35f3cbe995 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/src/core/server/http/base_path_proxy_server.ts @@ -143,6 +143,7 @@ export class BasePathProxyServer { return responseToolkit.continue; }, ], + validate: { payload: true }, }, path: `${this.httpConfig.basePath}/{kbnPath*}`, }); @@ -175,6 +176,7 @@ export class BasePathProxyServer { return responseToolkit.continue; }, ], + validate: { payload: true }, }, path: `/__UNSAFE_bypassBasePath/{kbnPath*}`, }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 3354324c12407..da97ab535516c 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -128,6 +128,8 @@ export class HttpServer { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); const { authRequired = true, tags } = route.options; + // Hapi does not allow payload validation to be specified for 'head' or 'get' requests + const validate = ['head', 'get'].includes(route.method) ? undefined : { payload: true }; this.server.route({ handler: route.handler, method: route.method, @@ -135,6 +137,11 @@ export class HttpServer { options: { auth: authRequired ? undefined : false, tags: tags ? Array.from(tags) : undefined, + // TODO: This 'validate' section can be removed once the legacy platform is completely removed. + // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default + // validation applied in ./http_tools#getServerOptions + // (All NP routes are already required to specify their own validation in order to access the payload) + validate, }, }); } diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 88164a76c66f0..22468a5b252f4 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -23,6 +23,7 @@ import Hoek from 'hoek'; import { ServerOptions as TLSOptions } from 'https'; import { ValidationError } from 'joi'; import { HttpConfig } from './http_config'; +import { validateObject } from './prototype_pollution'; /** * Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server. @@ -45,6 +46,11 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = { options: { abortEarly: false, }, + // TODO: This payload validation can be removed once the legacy platform is completely removed. + // This is a default payload validation which applies to all LP routes which do not specify their own + // `validate.payload` handler, in order to reduce the likelyhood of prototype pollution vulnerabilities. + // (All NP routes are already required to specify their own validation in order to access the payload) + payload: value => Promise.resolve(validateObject(value)), }, }, state: { diff --git a/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap b/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap new file mode 100644 index 0000000000000..937e040c771ee --- /dev/null +++ b/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can't submit {"__proto__":null} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"constructor":{"prototype":null}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"__proto__":true}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"__proto__":{}}}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"constructor":{"prototype":null}}}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"constructor":{"prototype":null}}} 1`] = `"'constructor.prototype' is an invalid key"`; diff --git a/src/legacy/core_plugins/expressions/public/expressions.ts b/src/core/server/http/prototype_pollution/index.ts similarity index 93% rename from src/legacy/core_plugins/expressions/public/expressions.ts rename to src/core/server/http/prototype_pollution/index.ts index 67ca3492f8244..e1a33ffba155e 100644 --- a/src/legacy/core_plugins/expressions/public/expressions.ts +++ b/src/core/server/http/prototype_pollution/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './index'; +export { validateObject } from './validate_object'; diff --git a/src/core/server/http/prototype_pollution/validate_object.test.ts b/src/core/server/http/prototype_pollution/validate_object.test.ts new file mode 100644 index 0000000000000..9e23d6cec6444 --- /dev/null +++ b/src/core/server/http/prototype_pollution/validate_object.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { validateObject } from './validate_object'; + +test(`fails on circular references`, () => { + const foo: Record = {}; + foo.myself = foo; + + expect(() => + validateObject({ + payload: foo, + }) + ).toThrowErrorMatchingInlineSnapshot(`"circular reference detected"`); +}); + +[ + { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + { + foo: { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + }, + { constructor: { foo: { prototype: null } } }, + { prototype: { foo: { constructor: null } } }, +].forEach(value => { + ['headers', 'payload', 'query', 'params'].forEach(property => { + const obj = { + [property]: value, + }; + test(`can submit ${JSON.stringify(obj)}`, () => { + expect(() => validateObject(obj)).not.toThrowError(); + }); + }); +}); + +// if we use the object literal syntax to create the following values, we end up +// actually reassigning the __proto__ which makes it be a non-enumerable not-own property +// which isn't what we want to test here +[ + JSON.parse(`{ "__proto__": null }`), + JSON.parse(`{ "foo": { "__proto__": true } }`), + JSON.parse(`{ "foo": { "bar": { "__proto__": {} } } }`), + JSON.parse(`{ "constructor": { "prototype" : null } }`), + JSON.parse(`{ "foo": { "constructor": { "prototype" : null } } }`), + JSON.parse(`{ "foo": { "bar": { "constructor": { "prototype" : null } } } }`), +].forEach(value => { + test(`can't submit ${JSON.stringify(value)}`, () => { + expect(() => validateObject(value)).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/core/server/http/prototype_pollution/validate_object.ts b/src/core/server/http/prototype_pollution/validate_object.ts new file mode 100644 index 0000000000000..cab6ce295ce92 --- /dev/null +++ b/src/core/server/http/prototype_pollution/validate_object.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ + +interface StackItem { + value: any; + previousKey: string | null; +} + +// we have to do Object.prototype.hasOwnProperty because when you create an object using +// Object.create(null), and I assume other methods, you get an object without a prototype, +// so you can't use current.hasOwnProperty +const hasOwnProperty = (obj: any, property: string) => + Object.prototype.hasOwnProperty.call(obj, property); + +const isObject = (obj: any) => typeof obj === 'object' && obj !== null; + +// we're using a stack instead of recursion so we aren't limited by the call stack +export function validateObject(obj: any) { + if (!isObject(obj)) { + return; + } + + const stack: StackItem[] = [ + { + value: obj, + previousKey: null, + }, + ]; + const seen = new WeakSet([obj]); + + while (stack.length > 0) { + const { value, previousKey } = stack.pop()!; + + if (!isObject(value)) { + continue; + } + + if (hasOwnProperty(value, '__proto__')) { + throw new Error(`'__proto__' is an invalid key`); + } + + if (hasOwnProperty(value, 'prototype') && previousKey === 'constructor') { + throw new Error(`'constructor.prototype' is an invalid key`); + } + + // iterating backwards through an array is reportedly more performant + const entries = Object.entries(value); + for (let i = entries.length - 1; i >= 0; --i) { + const [key, childValue] = entries[i]; + if (isObject(childValue)) { + if (seen.has(childValue)) { + throw new Error('circular reference detected'); + } + + seen.add(childValue); + } + + stack.push({ + value: childValue, + previousKey: key, + }); + } + } +} diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 6f5cb02fd8cba..2c3dfedd1d181 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -52,7 +52,7 @@ export type RequestHandlerContextProvider< * * @example * To handle an incoming request in your plugin you should: - * - Create a `Router` instance. Router is already configured to use `plugin-id` to prefix path segment for your routes. + * - Create a `Router` instance. * ```ts * const router = httpSetup.createRouter(); * ``` @@ -87,7 +87,7 @@ export type RequestHandlerContextProvider< * } * ``` * - * - Register route handler for GET request to 'my-app/path/{id}' path + * - Register route handler for GET request to 'path/{id}' path * ```ts * import { schema, TypeOf } from '@kbn/config-schema'; * const router = httpSetup.createRouter(); @@ -184,7 +184,7 @@ export interface HttpServiceSetup { * @example * ```ts * const router = createRouter(); - * // handler is called when '${my-plugin-id}/path' resource is requested with `GET` method + * // handler is called when '/path' resource is requested with `GET` method * router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); * ``` * @public diff --git a/src/core/utils/poller.test.ts b/src/core/utils/poller.test.ts deleted file mode 100644 index df89f7341c956..0000000000000 --- a/src/core/utils/poller.test.ts +++ /dev/null @@ -1,71 +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 { Poller } from './poller'; - -const delay = (duration: number) => new Promise(r => setTimeout(r, duration)); - -// FLAKY: https://github.com/elastic/kibana/issues/44560 -describe.skip('Poller', () => { - let handler: jest.Mock; - let poller: Poller; - - beforeEach(() => { - handler = jest.fn().mockImplementation((iteration: number) => `polling-${iteration}`); - poller = new Poller(100, 'polling', handler); - }); - - afterEach(() => { - poller.unsubscribe(); - }); - - it('returns an observable of subject', async () => { - await delay(300); - expect(poller.subject$.getValue()).toBe('polling-2'); - }); - - it('executes a function on an interval', async () => { - await delay(300); - expect(handler).toBeCalledTimes(3); - }); - - it('no longer polls after unsubscribing', async () => { - await delay(300); - poller.unsubscribe(); - await delay(300); - expect(handler).toBeCalledTimes(3); - }); - - it('does not add next value if returns undefined', async () => { - const values: any[] = []; - const polling = new Poller(100, 'polling', iteration => { - if (iteration % 2 === 0) { - return `polling-${iteration}`; - } - }); - - polling.subject$.subscribe(value => { - values.push(value); - }); - await delay(300); - polling.unsubscribe(); - - expect(values).toEqual(['polling', 'polling-0', 'polling-2']); - }); -}); diff --git a/src/core/utils/poller.ts b/src/core/utils/poller.ts deleted file mode 100644 index 7c50db74bcefb..0000000000000 --- a/src/core/utils/poller.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { BehaviorSubject, timer } from 'rxjs'; - -/** - * Create an Observable BehaviorSubject to invoke a function on an interval - * which returns the next value for the observable. - * @public - */ -export class Poller { - /** - * The observable to observe for changes to the poller value. - */ - public readonly subject$ = new BehaviorSubject(this.initialValue); - private poller$ = timer(0, this.frequency); - private subscription = this.poller$.subscribe(async iteration => { - const next = await this.handler(iteration); - - if (next !== undefined) { - this.subject$.next(next); - } - - return iteration; - }); - - constructor( - private frequency: number, - private initialValue: T, - private handler: (iteration: number) => Promise | T | undefined - ) {} - - /** - * Permanently end the polling operation. - */ - unsubscribe() { - return this.subscription.unsubscribe(); - } -} diff --git a/src/dev/i18n/extractors/code.js b/src/dev/i18n/extractors/code.js index fa0d834824e97..6439f8ceff332 100644 --- a/src/dev/i18n/extractors/code.js +++ b/src/dev/i18n/extractors/code.js @@ -67,7 +67,16 @@ export function* extractCodeMessages(buffer, reporter) { try { ast = parse(buffer.toString(), { sourceType: 'module', - plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators', 'dynamicImport'], + plugins: [ + 'jsx', + 'typescript', + 'objectRestSpread', + 'classProperties', + 'asyncGenerators', + 'dynamicImport', + 'nullishCoalescingOperator', + 'optionalChaining', + ], }); } catch (error) { if (error instanceof SyntaxError) { diff --git a/src/es_archiver/lib/indices/delete_index.js b/src/es_archiver/lib/indices/delete_index.js index 44a83be741063..b732989f02cb6 100644 --- a/src/es_archiver/lib/indices/delete_index.js +++ b/src/es_archiver/lib/indices/delete_index.js @@ -32,7 +32,7 @@ export async function deleteIndex(options) { stats, index, log, - retryIfSnapshottingCount = 3 + retryIfSnapshottingCount = 10 } = options; const getIndicesToDelete = async () => { diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index ab96b69851b71..f054c4d53fd8d 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -17,9 +17,10 @@ * under the License. */ -import { castEsToKbnFieldTypeName } from '../plugins/data/common'; -// eslint-disable-next-line max-len -import { shouldReadFieldFromDocValues } from '../plugins/data/server'; +import { + shouldReadFieldFromDocValues, + castEsToKbnFieldTypeName, +} from '../plugins/data/server'; function stubbedLogstashFields() { return [ diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx index 87558a73087d8..03d5b3f1d8f44 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -62,10 +62,6 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { updateCurrentState: () => {}, }, }, - // eslint-disable-next-line - ResizeChecker: function() { - return { on: () => {} }; - }, docLinkVersion: 'NA', }; editor = mount( diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx index b2a38a996f6a2..10f1ef34602a6 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -63,7 +63,6 @@ const DEFAULT_INPUT_VALUE = `GET _search function _Editor({ previousStateLocation = 'stored' }: EditorProps) { const { services: { history, notifications }, - ResizeChecker, docLinkVersion, } = useAppContext(); @@ -130,7 +129,6 @@ function _Editor({ previousStateLocation = 'stored' }: EditorProps) { mappings.retrieveAutoCompleteInfo(); const unsubscribeResizer = subscribeResizeChecker( - ResizeChecker, editorRef.current!, editorInstanceRef.current ); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx index fcf9f17e3ebd7..d38e86df41464 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -31,7 +31,6 @@ function _EditorOuput() { const editorInstanceRef = useRef(null); const { services: { settings }, - ResizeChecker, } = useAppContext(); const dispatch = useEditorActionContext(); @@ -42,11 +41,7 @@ function _EditorOuput() { const editor$ = $(editorRef.current!); editorInstanceRef.current = initializeOutput(editor$, settings); editorInstanceRef.current.update(''); - const unsubscribe = subscribeResizeChecker( - ResizeChecker, - editorRef.current!, - editorInstanceRef.current - ); + const unsubscribe = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current); dispatch({ type: 'setOutputEditor', value: editorInstanceRef.current }); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx index 881b59e6b3a1c..fdfe9ecc7b94c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx @@ -45,7 +45,6 @@ const CHILD_ELEMENT_PREFIX = 'historyReq'; export function ConsoleHistory({ close }: Props) { const { services: { history }, - ResizeChecker, } = useAppContext(); const dispatch = useEditorActionContext(); @@ -200,7 +199,6 @@ export function ConsoleHistory({ close }: Props) {
diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx index d531e143a79d0..c15bec0563049 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx @@ -31,10 +31,9 @@ import { applyCurrentSettings } from '../console_editor/apply_editor_settings'; interface Props { settings: DevToolsSettings; req: any | null; - ResizeChecker: any; } -export function HistoryViewer({ settings, ResizeChecker, req }: Props) { +export function HistoryViewer({ settings, req }: Props) { const divRef = useRef(null); const viewerRef = useRef(null); @@ -43,7 +42,7 @@ export function HistoryViewer({ settings, ResizeChecker, req }: Props) { viewerRef.current = viewer; viewer.renderer.setShowPrintMargin(false); viewer.$blockScrolling = Infinity; - const unsubscribe = subscribeResizeChecker(ResizeChecker, divRef.current!, viewer); + const unsubscribe = subscribeResizeChecker(divRef.current!, viewer); return () => unsubscribe(); }, []); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts index c83c593ef404d..4ecd5d415833c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +import { ResizeChecker } from '../../../../../../../../../plugins/kibana_utils/public'; -export function subscribeResizeChecker(ResizeChecker: any, $el: any, ...editors: any[]) { - const checker = new ResizeChecker($el); +export function subscribeResizeChecker(el: HTMLElement, ...editors: any[]) { + const checker = new ResizeChecker(el); checker.on('resize', () => editors.forEach(e => { e.resize(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx b/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx index 7bbdf731407e3..be7aa87ac2894 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx @@ -29,7 +29,6 @@ interface ContextValue { notifications: NotificationsSetup; }; docLinkVersion: string; - ResizeChecker: any; } interface ContextProps { diff --git a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx b/src/legacy/core_plugins/console/np_ready/public/application/index.tsx index d8933e60470c2..aaacfd3894d18 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/index.tsx @@ -32,10 +32,9 @@ export function legacyBackDoorToSettings() { export function boot(deps: { docLinkVersion: string; I18nContext: any; - ResizeChecker: any; notifications: NotificationsSetup; }) { - const { I18nContext, ResizeChecker, notifications, docLinkVersion } = deps; + const { I18nContext, notifications, docLinkVersion } = deps; const storage = createStorage({ engine: window.localStorage, @@ -51,7 +50,6 @@ export function boot(deps: { value={{ docLinkVersion, services: { storage, history, settings, notifications }, - ResizeChecker, }} > diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index 8c60ff23648be..463aac74da944 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -26,7 +26,6 @@ import 'brace/mode/text'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { npSetup, npStart } from 'ui/new_platform'; import { I18nContext } from 'ui/i18n'; -import { ResizeChecker } from 'ui/resize_checker'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export interface XPluginSet { @@ -34,7 +33,6 @@ export interface XPluginSet { feature_catalogue: FeatureCatalogueSetup; __LEGACY: { I18nContext: any; - ResizeChecker: any; }; } @@ -48,7 +46,6 @@ pluginInstance.setup(npSetup.core, { ...npSetup.plugins, __LEGACY: { I18nContext, - ResizeChecker, }, }); pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/np_ready/public/plugin.ts index f02b0b5e72999..301b85b6e7395 100644 --- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/console/np_ready/public/plugin.ts @@ -30,7 +30,7 @@ export class ConsoleUIPlugin implements Plugin { async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) { const { - __LEGACY: { I18nContext, ResizeChecker }, + __LEGACY: { I18nContext }, devTools, feature_catalogue, } = pluginSet; @@ -62,7 +62,6 @@ export class ConsoleUIPlugin implements Plugin { boot({ docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, I18nContext, - ResizeChecker, notifications, }), element diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.js index 8ce828879a677..856128f3d4c03 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.js @@ -71,6 +71,7 @@ export const createProxyRoute = ({ parse: false, }, validate: { + payload: true, query: Joi.object() .keys({ method: Joi.string() diff --git a/src/legacy/core_plugins/console/server/request.test.ts b/src/legacy/core_plugins/console/server/request.test.ts index 463649a090295..d5504c0f3a3c2 100644 --- a/src/legacy/core_plugins/console/server/request.test.ts +++ b/src/legacy/core_plugins/console/server/request.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import http from 'http'; +import http, { ClientRequest } from 'http'; import * as sinon from 'sinon'; import { sendRequest } from './request'; import { URL } from 'url'; @@ -24,7 +24,7 @@ import { fail } from 'assert'; describe(`Console's send request`, () => { let sandbox: sinon.SinonSandbox; - let stub: sinon.SinonStub; + let stub: sinon.SinonStub, ClientRequest>; let fakeRequest: http.ClientRequest; beforeEach(() => { diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts index a985d3f023108..71f2fa5ffec7c 100644 --- a/src/legacy/core_plugins/data/index.ts +++ b/src/legacy/core_plugins/data/index.ts @@ -35,6 +35,7 @@ export default function DataPlugin(kibana: any) { }, init: (server: Legacy.Server) => ({}), uiExports: { + interpreter: ['plugins/data/search/expressions/boot'], injectDefaultVars: () => ({}), styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index abe9ec6d6e873..39ec1f78b65f0 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; +import { toMountPoint } from '../../../../../../plugins/kibana_react/public'; import { IAction, createAction, @@ -79,17 +80,19 @@ export function createFilterAction( const filterSelectionPromise: Promise = new Promise(resolve => { const overlay = overlays.openModal( - applyFiltersPopover( - filters, - indexPatterns, - () => { - overlay.close(); - resolve([]); - }, - (filterSelection: esFilters.Filter[]) => { - overlay.close(); - resolve(filterSelection); - } + toMountPoint( + applyFiltersPopover( + filters, + indexPatterns, + () => { + overlay.close(); + resolve([]); + }, + (filterSelection: esFilters.Filter[]) => { + overlay.close(); + resolve(filterSelection); + } + ) ), { 'data-test-subj': 'test', diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx index e9d05d6340e58..ab52d56841612 100644 --- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx +++ b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx @@ -31,7 +31,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { IndexPattern } from '../../index_patterns'; -import { getFilterDisplayText } from '../filter_bar/filter_editor/lib/get_filter_display_text'; +import { FilterLabel } from '../filter_bar/filter_editor/lib/filter_label'; import { mapAndFlattenFilters, esFilters } from '../../../../../../plugins/data/public'; import { getDisplayValueFromFilter } from '../filter_bar/filter_editor/lib/get_display_value'; @@ -58,8 +58,8 @@ export class ApplyFiltersPopoverContent extends Component { }; } private getLabel(filter: esFilters.Filter) { - const filterDisplayValue = getDisplayValueFromFilter(filter, this.props.indexPatterns); - return getFilterDisplayText(filter, filterDisplayValue); + const valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); + return ; } public render() { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 333e1e328651d..5b389f5b98aba 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -22,13 +22,12 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; import { CoreStart } from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; import { IndexPattern } from '../../index_patterns'; import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; import { useKibana, KibanaContextProvider } from '../../../../../../plugins/kibana_react/public'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { DataPublicPluginStart, esFilters } from '../../../../../../plugins/data/public'; interface Props { filters: esFilters.Filter[]; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/__snapshots__/filter_label.test.js.snap b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/__snapshots__/filter_label.test.js.snap new file mode 100644 index 0000000000000..b0bcec12ca3b7 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/__snapshots__/filter_label.test.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`alias 1`] = ` + + geo.coordinates in US + +`; + +exports[`negated alias 1`] = ` + + + NOT + + geo.coordinates in US + +`; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.test.js b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.test.js new file mode 100644 index 0000000000000..042a353031c33 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.test.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FilterLabel } from './filter_label'; +import { phraseFilter } from './fixtures/phrase_filter'; +import { shallow } from 'enzyme'; + +test('alias', () => { + const filter = { + ...phraseFilter, + meta: { + ...phraseFilter.meta, + alias: 'geo.coordinates in US', + } + }; + const component = shallow(); + expect(component).toMatchSnapshot(); +}); + +test('negated alias', () => { + const filter = { + ...phraseFilter, + meta: { + ...phraseFilter.meta, + alias: 'geo.coordinates in US', + negate: true, + } + }; + const component = shallow(); + expect(component).toMatchSnapshot(); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx similarity index 83% rename from src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx rename to src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx index 21abcd8510046..d16158226579c 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_filter_display_text.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx @@ -23,7 +23,12 @@ import { i18n } from '@kbn/i18n'; import { existsOperator, isOneOfOperator } from './filter_operators'; import { esFilters } from '../../../../../../../../plugins/data/public'; -export function getFilterDisplayText(filter: esFilters.Filter, filterDisplayName: string) { +interface Props { + filter: esFilters.Filter; + valueLabel?: string; +} + +export function FilterLabel({ filter, valueLabel }: Props) { const prefixText = filter.meta.negate ? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', { defaultMessage: 'NOT ', @@ -37,7 +42,12 @@ export function getFilterDisplayText(filter: esFilters.Filter, filterDisplayName ); if (filter.meta.alias !== null) { - return `${prefix}${filter.meta.alias}`; + return ( + + {prefix} + {filter.meta.alias} + + ); } switch (filter.meta.type) { @@ -52,35 +62,35 @@ export function getFilterDisplayText(filter: esFilters.Filter, filterDisplayName return ( {prefix} - {filter.meta.key}: {filterDisplayName} + {filter.meta.key}: {valueLabel} ); case 'geo_polygon': return ( {prefix} - {filter.meta.key}: {filterDisplayName} + {filter.meta.key}: {valueLabel} ); case 'phrase': return ( {prefix} - {filter.meta.key}: {filterDisplayName} + {filter.meta.key}: {valueLabel} ); case 'phrases': return ( {prefix} - {filter.meta.key} {isOneOfOperator.message} {filterDisplayName} + {filter.meta.key} {isOneOfOperator.message} {valueLabel} ); case 'query_string': return ( {prefix} - {filterDisplayName} + {valueLabel} ); case 'range': @@ -88,7 +98,7 @@ export function getFilterDisplayText(filter: esFilters.Filter, filterDisplayName return ( {prefix} - {filter.meta.key}: {filterDisplayName} + {filter.meta.key}: {valueLabel} ); default: diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx index 50c1672333801..0dbe92dcb0da6 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx @@ -62,9 +62,9 @@ class FilterItemUI extends Component { this.props.className ); - const displayName = getDisplayValueFromFilter(filter, this.props.indexPatterns); + const valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value ? `filter-value-${displayName}` : ''; + const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; const dataTestSubjDisabled = `filter-${ this.props.filter.meta.disabled ? 'disabled' : 'enabled' }`; @@ -72,7 +72,7 @@ class FilterItemUI extends Component { const badge = ( this.props.onRemove()} onClick={this.togglePopover} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx index 6421691c4ef41..fee043764d8d7 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx @@ -20,12 +20,12 @@ import { EuiBadge, useInnerText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { SFC } from 'react'; -import { getFilterDisplayText } from '../filter_editor/lib/get_filter_display_text'; +import { FilterLabel } from '../filter_editor/lib/filter_label'; import { esFilters } from '../../../../../../../plugins/data/public'; interface Props { filter: esFilters.Filter; - displayName: string; + valueLabel: string; [propName: string]: any; } @@ -33,11 +33,10 @@ export const FilterView: SFC = ({ filter, iconOnClick, onClick, - displayName, + valueLabel, ...rest }: Props) => { const [ref, innerText] = useInnerText(); - const displayText = {getFilterDisplayText(filter, displayName)}; let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { defaultMessage: 'Filter: {innerText}. Select for more filter actions.', @@ -75,7 +74,9 @@ export const FilterView: SFC = ({ })} {...rest} > - {displayText} + + + ); }; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts index dc5023795bf19..6084b4c106452 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts @@ -22,40 +22,24 @@ import { fieldFormats } from 'ui/registry/field_formats'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; -import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; // @ts-ignore import { shortenDottedString } from '../../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; import { IndexPattern } from '../index_patterns'; import { getNotifications } from '../services'; -import { getKbnFieldType } from '../../../../../../plugins/data/public'; - -interface FieldSubType { - multi?: { parent: string }; - nested?: { path: string }; -} +import { + FieldFormat, + getKbnFieldType, + IFieldType, + IFieldSubType, +} from '../../../../../../plugins/data/public'; export type FieldSpec = Record; -export interface FieldType { - name: string; - type: string; - script?: string; - lang?: string; - count?: number; - // esTypes might be undefined on old index patterns that have not been refreshed since we added - // this prop. It is also undefined on scripted fields. - esTypes?: string[]; - aggregatable?: boolean; - filterable?: boolean; - searchable?: boolean; - sortable?: boolean; - visualizable?: boolean; - readFromDocValues?: boolean; - scripted?: boolean; - subType?: FieldSubType; - displayName?: string; - format?: any; -} + +/** @deprecated + * Please use IFieldType instead + * */ +export type FieldType = IFieldType; export class Field implements FieldType { name: string; @@ -72,7 +56,7 @@ export class Field implements FieldType { sortable?: boolean; visualizable?: boolean; scripted?: boolean; - subType?: FieldSubType; + subType?: IFieldSubType; displayName?: string; format: any; routes: Record = { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index bf0d79e960d9b..12aa3c2fb0d51 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -37,21 +37,18 @@ import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { ES_FIELD_TYPES, IIndexPattern } from '../../../../../../plugins/data/public'; import { getNotifications } from '../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; -export interface StaticIndexPattern { - fields: FieldType[]; - title: string; - id?: string; - type?: string; - timeFieldName?: string; -} +/** @deprecated + * Please use IIndexPattern instead + * */ +export type StaticIndexPattern = IIndexPattern; -export class IndexPattern implements StaticIndexPattern { +export class IndexPattern implements IIndexPattern { [key: string]: any; public id?: string; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx index ca0ac3c371849..cd64b1ecf2549 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx @@ -82,8 +82,11 @@ function QueryBarTopRowUI(props: Props) { const queryLanguage = props.query && props.query.language; const persistedLog: PersistedLog | undefined = React.useMemo( - () => (queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined), - [queryLanguage] + () => + queryLanguage && uiSettings && storage && appName + ? getQueryLog(uiSettings!, storage, appName, queryLanguage) + : undefined, + [appName, queryLanguage, uiSettings, storage] ); function onClickSubmitButton(event: React.MouseEvent) { diff --git a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts b/src/legacy/core_plugins/data/public/search/expressions/boot.ts similarity index 85% rename from src/legacy/core_plugins/interpreter/common/lib/fonts.ts rename to src/legacy/core_plugins/data/public/search/expressions/boot.ts index 1594f42abf2eb..29348383ce6fe 100644 --- a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/boot.ts @@ -17,5 +17,7 @@ * under the License. */ -// eslint-disable-next-line -export * from '../../../../../plugins/expressions/public/fonts'; +import { npSetup } from 'ui/new_platform'; +import { esaggs } from './esaggs'; + +npSetup.plugins.expressions.registerFunction(esaggs); diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts similarity index 87% rename from src/legacy/core_plugins/interpreter/public/functions/esaggs.ts rename to src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index bcb8d00663e01..db2a803ea1c61 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -22,22 +22,32 @@ import { i18n } from '@kbn/i18n'; import { AggConfigs } from 'ui/agg_types/agg_configs'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import chrome from 'ui/chrome'; - import { Query, TimeRange, esFilters } from 'src/plugins/data/public'; -import { SearchSource } from '../../../../ui/public/courier/search_source'; +import { + KibanaContext, + KibanaDatatable, + ExpressionFunction, + KibanaDatatableColumn, +} from 'src/plugins/expressions/public'; +import { SearchSource } from '../../../../../ui/public/courier/search_source'; // @ts-ignore import { FilterBarQueryFilterProvider, QueryFilter, -} from '../../../../ui/public/filter_manager/query_filter'; +} from '../../../../../ui/public/filter_manager/query_filter'; -import { buildTabularInspectorData } from '../../../../ui/public/inspector/build_tabular_inspector_data'; +import { buildTabularInspectorData } from '../../../../../ui/public/inspector/build_tabular_inspector_data'; import { getRequestInspectorStats, getResponseInspectorStats, -} from '../../../../ui/public/courier/utils/courier_inspector_utils'; -import { calculateObjectHash } from '../../../../ui/public/vis/lib/calculate_object_hash'; -import { getTime } from '../../../../ui/public/timefilter'; +} from '../../../../../ui/public/courier/utils/courier_inspector_utils'; +import { calculateObjectHash } from '../../../../../ui/public/vis/lib/calculate_object_hash'; +import { getTime } from '../../../../../ui/public/timefilter'; +// @ts-ignore +import { tabifyAggResponse } from '../../../../../ui/public/agg_response/tabify/tabify'; +import { start as data } from '../../../../data/public/legacy'; +import { PersistedState } from '../../../../../ui/public/persisted_state'; +import { Adapters } from '../../../../../../plugins/inspector/public'; export interface RequestHandlerParams { searchSource: SearchSource; @@ -55,14 +65,6 @@ export interface RequestHandlerParams { abortSignal?: AbortSignal; } -// @ts-ignore -import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify'; -import { KibanaContext, KibanaDatatable } from '../../common'; -import { ExpressionFunction, KibanaDatatableColumn } from '../../types'; -import { start as data } from '../../../data/public/legacy'; -import { PersistedState } from '../../../../ui/public/persisted_state'; -import { Adapters } from '../../../../../plugins/inspector/public'; - const name = 'esaggs'; type Context = KibanaContext | null; @@ -140,17 +142,14 @@ const handleCourierRequest = async ({ if (shouldQuery) { inspectorAdapters.requests.reset(); const request = inspectorAdapters.requests.start( - i18n.translate('interpreter.functions.esaggs.inspector.dataRequest.title', { + i18n.translate('data.functions.esaggs.inspector.dataRequest.title', { defaultMessage: 'Data', }), { - description: i18n.translate( - 'interpreter.functions.esaggs.inspector.dataRequest.description', - { - defaultMessage: - 'This request queries Elasticsearch to fetch the data for the visualization.', - } - ), + description: i18n.translate('data.functions.esaggs.inspector.dataRequest.description', { + defaultMessage: + 'This request queries Elasticsearch to fetch the data for the visualization.', + }), } ); request.stats(getRequestInspectorStats(requestSearchSource)); @@ -229,7 +228,7 @@ export const esaggs = (): ExpressionFunction; @@ -72,6 +76,7 @@ export interface SearchBarOwnProps { // Show when user has privileges to save showSaveQuery?: boolean; savedQuery?: SavedQuery; + onQueryChange?: (payload: { dateRange: TimeRange; query?: Query }) => void; onQuerySubmit?: (payload: { dateRange: TimeRange; query?: Query }) => void; // User has saved the current state as a saved query onSaved?: (savedQuery: SavedQuery) => void; @@ -206,6 +211,18 @@ class SearchBarUI extends Component { ); } + /* + * This Function is here to show the toggle in saved query form + * in case you the date range (from/to) + */ + private shouldRenderTimeFilterInSavedQueryForm() { + const { dateRangeFrom, dateRangeTo, showDatePicker } = this.props; + return ( + showDatePicker || + (!showDatePicker && dateRangeFrom !== undefined && dateRangeTo !== undefined) + ); + } + public setFilterBarHeight = () => { requestAnimationFrame(() => { const height = @@ -299,6 +316,9 @@ class SearchBarUI extends Component { dateRangeFrom: queryAndDateRange.dateRange.from, dateRangeTo: queryAndDateRange.dateRange.to, }); + if (this.props.onQueryChange) { + this.props.onQueryChange(queryAndDateRange); + } }; public onQueryBarSubmit = (queryAndDateRange: { dateRange?: TimeRange; query?: Query }) => { @@ -440,7 +460,7 @@ class SearchBarUI extends Component { onSave={this.onSave} onClose={() => this.setState({ showSaveQueryModal: false })} showFilterOption={this.props.showFilterBar} - showTimeFilterOption={this.props.showDatePicker} + showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()} /> ) : null} {this.state.showSaveNewQueryModal ? ( @@ -449,7 +469,7 @@ class SearchBarUI extends Component { onSave={savedQueryMeta => this.onSave(savedQueryMeta, true)} onClose={() => this.setState({ showSaveNewQueryModal: false })} showFilterOption={this.props.showFilterBar} - showTimeFilterOption={this.props.showDatePicker} + showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()} /> ) : null}
diff --git a/src/legacy/core_plugins/expressions/package.json b/src/legacy/core_plugins/expressions/package.json deleted file mode 100644 index 1b2b2fd50097c..0000000000000 --- a/src/legacy/core_plugins/expressions/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "expressions", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/kibana.json b/src/legacy/core_plugins/expressions/public/np_ready/kibana.json deleted file mode 100644 index ec87b56f3745e..0000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/kibana.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "expressions", - "version": "kibana", - "server": false, - "ui": true, - "requiredPlugins": [ - "inspector" - ] -} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss b/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss deleted file mode 100644 index b9df491cd6e79..0000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './expression_renderer'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts deleted file mode 100644 index 2ff71a6df60cf..0000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts +++ /dev/null @@ -1,29 +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 { PluginInitializerContext } from '../../../../../../core/public'; -import { ExpressionsPublicPlugin } from './plugin'; - -export * from './plugin'; -export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer'; -export { IInterpreterRenderFunction, IInterpreterRenderHandlers } from './types'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new ExpressionsPublicPlugin(initializerContext); -} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts deleted file mode 100644 index d2c6c14c17de1..0000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts +++ /dev/null @@ -1,95 +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. - */ - -/* eslint-disable */ -import { npSetup } from 'ui/new_platform'; -/* eslint-enable */ - -import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; - -import { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, -} from '../../../../../../core/public'; -import { - Start as InspectorStart, - Setup as InspectorSetup, -} from '../../../../../../plugins/inspector/public'; -import { ExpressionInterpreter } from './types'; -import { setInterpreter, setInspector, setRenderersRegistry } from './services'; -import { ExpressionRendererImplementation } from './expression_renderer'; -import { ExpressionLoader, loader } from './loader'; -import { ExpressionDataHandler, execute } from './execute'; -import { ExpressionRenderHandler, render } from './render'; - -export interface ExpressionsSetupDeps { - inspector: InspectorSetup; -} - -export interface ExpressionsStartDeps { - inspector: InspectorStart; -} - -export { ExpressionsSetup }; -export type ExpressionsStart = ReturnType; - -export class ExpressionsPublicPlugin - implements - Plugin { - constructor(initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup, plugins: ExpressionsSetupDeps): ExpressionsSetup { - setRenderersRegistry(npSetup.plugins.expressions.__LEGACY.renderers); - - // eslint-disable-next-line - const { getInterpreter } = require('../../../../interpreter/public/interpreter'); - getInterpreter() - .then(({ interpreter }: { interpreter: ExpressionInterpreter }) => { - setInterpreter(interpreter); - }) - .catch((e: Error) => { - throw new Error('interpreter is not initialized'); - }); - - return { - registerType: npSetup.plugins.expressions.registerType, - registerFunction: npSetup.plugins.expressions.registerFunction, - registerRenderer: npSetup.plugins.expressions.registerRenderer, - __LEGACY: npSetup.plugins.expressions.__LEGACY, - }; - } - - public start(core: CoreStart, { inspector }: ExpressionsStartDeps) { - setInspector(inspector); - - return { - execute, - render, - loader, - ExpressionRenderer: ExpressionRendererImplementation, - ExpressionDataHandler, - ExpressionRenderHandler, - ExpressionLoader, - }; - } - - public stop() {} -} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts deleted file mode 100644 index 5c357b5dcd2bb..0000000000000 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts +++ /dev/null @@ -1,31 +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 { ExpressionInterpreter } from './types'; -import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; -import { Start as IInspector } from '../../../../../../plugins/inspector/public'; -import { ExpressionsSetup } from './plugin'; - -export const [getInspector, setInspector] = createGetterSetter('Inspector'); -export const [getInterpreter, setInterpreter] = createGetterSetter( - 'Interpreter' -); -export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< - ExpressionsSetup['__LEGACY']['renderers'] ->('Renderers registry'); diff --git a/src/legacy/core_plugins/expressions/public/plugin.ts b/src/legacy/core_plugins/expressions/public/plugin.ts deleted file mode 100644 index 93577757fecee..0000000000000 --- a/src/legacy/core_plugins/expressions/public/plugin.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './np_ready/public/plugin'; diff --git a/src/legacy/core_plugins/expressions/server/index.ts b/src/legacy/core_plugins/expressions/server/index.ts deleted file mode 100644 index 9880b336e76e5..0000000000000 --- a/src/legacy/core_plugins/expressions/server/index.ts +++ /dev/null @@ -1,18 +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. - */ diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js index 1d012f155ed73..3fe0d952227e7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js @@ -18,7 +18,9 @@ */ jest.mock('ui/new_platform'); -import { functionWrapper } from '../../interpreter/test_helpers'; + +// eslint-disable-next-line +import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; import { inputControlVis } from './input_control_fn'; describe('interpreter/functions#input_control_vis', () => { diff --git a/src/legacy/core_plugins/interpreter/README.md b/src/legacy/core_plugins/interpreter/README.md new file mode 100644 index 0000000000000..1a5cefbe0ed81 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/README.md @@ -0,0 +1,22 @@ +Interpreter legacy plugin has been migrated to the New Platform. Use +`expressions` New Platform plugin instead. + +In the New Platform: + +```ts +class MyPlugin { + setup(core, { expressions }) { + expressions.registerFunction(myFunction); + } + start(core, { expressions }) { + } +} +``` + +In the Legacy Platform: + +```ts +import { npSetup, npStart } from 'ui/new_platform'; + +npSetup.plugins.expressions.registerFunction(myFunction); +``` diff --git a/src/legacy/core_plugins/interpreter/index.ts b/src/legacy/core_plugins/interpreter/index.ts index 9427a2f8a2d0f..db6f17a2960a9 100644 --- a/src/legacy/core_plugins/interpreter/index.ts +++ b/src/legacy/core_plugins/interpreter/index.ts @@ -31,6 +31,7 @@ export default function InterpreterPlugin(kibana: any) { injectDefaultVars: server => ({ serverBasePath: server.config().get('server.basePath'), }), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, config: (Joi: any) => { return Joi.object({ diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts index f09ffd4697e74..768d76fbf744e 100644 --- a/src/legacy/core_plugins/interpreter/init.ts +++ b/src/legacy/core_plugins/interpreter/init.ts @@ -25,9 +25,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common // @ts-ignore import { routes } from './server/routes'; -import { typeSpecs as types } from '../../../plugins/expressions/public'; - -import { Type } from './common'; +import { typeSpecs as types, Type } from '../../../plugins/expressions/common'; import { Legacy } from '../../../../kibana'; export class TypesRegistry extends Registry { diff --git a/src/legacy/core_plugins/interpreter/public/functions/clog.ts b/src/legacy/core_plugins/interpreter/public/functions/clog.ts deleted file mode 100644 index 65362fe363374..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/clog.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/expressions/public/functions/clog'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/font.ts b/src/legacy/core_plugins/interpreter/public/functions/font.ts deleted file mode 100644 index 2f3bc1ff37e2c..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/font.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/expressions/public/functions/font'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts b/src/legacy/core_plugins/interpreter/public/functions/kibana.ts deleted file mode 100644 index 7da284d9672a5..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/expressions/public/functions/kibana'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/range.ts b/src/legacy/core_plugins/interpreter/public/functions/range.ts deleted file mode 100644 index 322bda983a8ff..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/range.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/visualizations/public/expression_functions/range'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts b/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts deleted file mode 100644 index d4ebeeb5267e1..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/visualizations/public/expression_functions/vis_dimension'; diff --git a/src/legacy/core_plugins/interpreter/public/index.scss b/src/legacy/core_plugins/interpreter/public/index.scss new file mode 100644 index 0000000000000..360f35020764d --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/index.scss @@ -0,0 +1,4 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +@import '../../../../plugins/expressions/public/index'; diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.ts b/src/legacy/core_plugins/interpreter/public/interpreter.ts index a337f7e4ebfea..71bce40ba8235 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.ts +++ b/src/legacy/core_plugins/interpreter/public/interpreter.ts @@ -22,13 +22,10 @@ import 'uiExports/interpreter'; import { register, registryFactory } from '@kbn/interpreter/common'; import { npSetup } from 'ui/new_platform'; import { registries } from './registries'; -import { visualization } from './renderers/visualization'; import { ExpressionInterpretWithHandlers, ExpressionExecutor, } from '../../../../plugins/expressions/public'; -import { esaggs as esaggsFn } from './functions/esaggs'; -import { visualization as visualizationFn } from './functions/visualization'; // Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins // can register without a transpile step. @@ -38,13 +35,6 @@ import { visualization as visualizationFn } from './functions/visualization'; registryFactory(registries) ); -// TODO: This needs to be moved to `data` plugin Search service. -registries.browserFunctions.register(esaggsFn); - -// TODO: This needs to be moved to `visualizations` plugin. -registries.browserFunctions.register(visualizationFn); -registries.renderers.register(visualization); - // TODO: This function will be left behind in the legacy platform. let executorPromise: Promise | undefined; export const getInterpreter = async () => { diff --git a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts b/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts deleted file mode 100644 index c14272fbf8def..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../../plugins/expressions/public/create_handlers'; diff --git a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts b/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts index 740b046610d9e..63e59ad613719 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts +++ b/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts @@ -19,9 +19,11 @@ import Boom from 'boom'; import Joi from 'joi'; -import { serializeProvider, API_ROUTE } from '../../common'; +import { serializeProvider } from '../../../../../plugins/expressions/common'; import { createHandlers } from '../lib/create_handlers'; +const API_ROUTE = '/api/interpreter'; + /** * Register the Canvas function endopints. * diff --git a/src/legacy/core_plugins/interpreter/test_helpers.ts b/src/legacy/core_plugins/interpreter/test_helpers.ts deleted file mode 100644 index 0e34f42b01544..0000000000000 --- a/src/legacy/core_plugins/interpreter/test_helpers.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../plugins/expressions/public/functions/tests/utils'; diff --git a/src/legacy/core_plugins/interpreter/types/arguments.ts b/src/legacy/core_plugins/interpreter/types/arguments.ts deleted file mode 100644 index 35566381d010d..0000000000000 --- a/src/legacy/core_plugins/interpreter/types/arguments.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../plugins/expressions/public/types/arguments'; diff --git a/src/legacy/core_plugins/interpreter/types/common.ts b/src/legacy/core_plugins/interpreter/types/common.ts deleted file mode 100644 index 99a7d2dc92f06..0000000000000 --- a/src/legacy/core_plugins/interpreter/types/common.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../plugins/expressions/public/types/common'; diff --git a/src/legacy/core_plugins/interpreter/types/functions.ts b/src/legacy/core_plugins/interpreter/types/functions.ts deleted file mode 100644 index 9a99a78281a0c..0000000000000 --- a/src/legacy/core_plugins/interpreter/types/functions.ts +++ /dev/null @@ -1,21 +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. - */ - -// eslint-disable-next-line -export * from '../../../../plugins/expressions/public/types/functions'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js index 07f24ebeff7e9..4113449cb5c81 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js @@ -17,7 +17,8 @@ * under the License. */ -import { functionWrapper } from '../../interpreter/test_helpers'; +// eslint-disable-next-line +import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; import { kibanaPie } from './pie_fn'; jest.mock('ui/new_platform'); 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 656b54040ad99..d5da4ba51e55b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -32,7 +32,6 @@ import { } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; -import { TimeRange, Query } from 'src/plugins/data/public'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { StaticIndexPattern, SavedQuery } from 'plugins/data'; @@ -42,7 +41,7 @@ import { Subscription } from 'rxjs'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; 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 d82b89339b0d0..457d8972876ae 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 @@ -35,7 +35,6 @@ import { docTitle } from 'ui/doc_title/doc_title'; import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { timefilter } from 'ui/timefilter'; @@ -55,6 +54,7 @@ import { SaveOptions } from 'ui/saved_objects/saved_object'; import { capabilities } from 'ui/capabilities'; import { Subscription } from 'rxjs'; import { npStart } from 'ui/new_platform'; +import { unhashUrl } from 'ui/state_management/state_hashing'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { Query } from '../../../../../plugins/data/public'; import { start as data } from '../../../data/public/legacy'; @@ -131,7 +131,6 @@ export class DashboardAppController { }) { const queryFilter = Private(FilterBarQueryFilterProvider); const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); let lastReloadRequestTime = 0; @@ -758,14 +757,13 @@ export class DashboardAppController { }); }; navActions[TopNavIds.SHARE] = anchorElement => { - showShareContextMenu({ + npStart.plugins.share.toggleShareContextMenu({ anchorElement, allowEmbed: true, allowShortUrl: !dashboardConfig.getHideWriteControls(), - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: dash.id, objectType: 'dashboard', - shareContextMenuExtensions: shareContextMenuExtensions.raw, sharingData: { title: dash.title, }, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 1a42ed837a9de..d5af4c93d0e0c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -27,9 +27,8 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import { Query } from 'src/plugins/data/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { Query, esFilters } from '../../../../../../src/plugins/data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; 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 8ee23bfb005a2..20a05e17d16d6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -50,7 +50,7 @@ import { migrateLegacyQuery, RequestAdapter, showSaveModal, - showShareContextMenu, + unhashUrl, stateMonitorFactory, subscribeWithScope, tabifyAggResponse, @@ -63,7 +63,7 @@ const { chrome, docTitle, FilterBarQueryFilterProvider, - ShareContextMenuExtensionsRegistryProvider, + share, StateProvider, timefilter, toastNotifications, @@ -190,7 +190,6 @@ function discoverController( ) { const responseHandler = vislibSeriesResponseHandlerProvider().handler; const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const queryFilter = Private(FilterBarQueryFilterProvider); @@ -323,14 +322,13 @@ function discoverController( testId: 'shareTopNavButton', run: async (anchorElement) => { const sharingData = await this.getSharingData(); - showShareContextMenu({ + share.toggleShareContextMenu({ anchorElement, allowEmbed: false, allowShortUrl: uiCapabilities.discover.createShortUrl, - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: savedSearch.id, objectType: 'search', - shareContextMenuExtensions, sharingData: { ...sharingData, title: savedSearch.title, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss index c0bcc3ab8a614..8b754d23f9604 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss @@ -62,6 +62,15 @@ doc-table { margin: $euiSizeXS $euiSizeXS 0; } +.kbnDocTable__bar--footer { + position: relative; + margin: -($euiSize * 3) $euiSizeXS 0; +} + +.kbnDocTable__padBottom { + padding-bottom: $euiSizeXL; +} + .kbnDocTable__error { display: flex; flex-direction: column; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html index b6ac1d3fd8b4a..61bb5cbf39cbe 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html @@ -1,5 +1,4 @@
@@ -26,32 +25,34 @@ >
- - - - +
+ - -
+ > + + + + +
-
+